Skip to content

Conversation

@grzesiek2010
Copy link
Member

@grzesiek2010 grzesiek2010 commented May 16, 2022

Closes #4893
Blocked by #5139

Most of the discussion around this PR is in the issue or here so no need to write this up really.

Before submitting this PR, please make sure you have:

  • run ./gradlew checkAll and confirmed all checks still pass OR confirm CircleCI build passes and run ./gradlew connectedDebugAndroidTest locally.
  • verified that any code or assets from external sources are properly credited in comments and/or in the about file.
  • verified that any new UI elements use theme colors. UI Components Style guidelines

@grzesiek2010
Copy link
Member Author

Ok @lognaturel @seadowg it's time to decide how to handle Mapbox 10. As we talked recently options are:

  • sharing our tokens so that everyone can build the app including CircleCi
  • storing Mapbox 10 in our local repository
  • having a separate flavor

I was more in favor of one of two first options because it would be easier. Then if it doesn't work for us or there is any problem with licenses we can add a separate flavor.
Please think about it one more time and we can discuss it.

// the Mapbox native library only for 32-bit and 64-bit ARM devices and
// omit it for all X86 devices.
exclude 'lib/x86/libmapbox-gl.so'
exclude 'lib/x86_64/libmapbox-gl.so'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are not going to like it. Mapbox 10 has libs with different naming. I could of course update it here and delete too but that causes crashes when the app is started so it looks like we no longer can do tricks like that. That increases the size of our apk of course...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What crash are you seeing? I think we'll want to do as much as can to avoid including unused x86 libs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with @seadowg that more details are needed here. What's the binary size without the filtering?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is:

Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/org.odk.collect.android-fpP4T_tgz8x_GU7w9kKqvA==/base.apk"],nativeLibraryDirectories=[/data/app/org.odk.collect.android-fpP4T_tgz8x_GU7w9kKqvA==/lib/x86_64, /data/app/org.odk.collect.android-fpP4T_tgz8x_GU7w9kKqvA==/base.apk!/lib/x86_64, /system/lib64, /system/product/lib64]]] couldn't find "libmapbox-maps.so"
        at java.lang.Runtime.loadLibrary0(Runtime.java:1067)
        at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
        at java.lang.System.loadLibrary(System.java:1667)
        at com.mapbox.common.loader.MapboxLibraryLoader.load(MapboxLibraryLoader.kt:19)
        at com.mapbox.maps.loader.MapboxMapsInitializer.create(MapboxMapsInitializer.java:19)
        at com.mapbox.maps.loader.MapboxMapsInitializer.create(MapboxMapsInitializer.java:16)
        at androidx.startup.AppInitializer.doInitialize(AppInitializer.java:180)
        at androidx.startup.AppInitializer.discoverAndInitialize(AppInitializer.java:238) 
        at androidx.startup.AppInitializer.discoverAndInitialize(AppInitializer.java:206) 
        at androidx.startup.InitializationProvider.onCreate(InitializationProvider.java:45) 
        at android.content.ContentProvider.attachInfo(ContentProvider.java:2092) 
        at android.content.ContentProvider.attachInfo(ContentProvider.java:2066) 
        at android.app.ActivityThread.installProvider(ActivityThread.java:6983) 
        at android.app.ActivityThread.installContentProviders(ActivityThread.java:6528) 
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6445) 
        at android.app.ActivityThread.access$1300(ActivityThread.java:219) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859) 

it takes place immediately when the app is started

Here is the size of those files:
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you haven't already, could you please review #3108 and #3118 to see if there are any ideas there?

The error you shared looks like it would be from a x86_64 device. Was it? I would not expect an arm device to give that error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this error is indeed only on architectures with missing libs (which it looks like and hopefully is), then that pushes us more towards not including Mapbox in all builds (using product flavours or my preferred conditional dependency method). That would allow us to continue to exclude x86 libs as we'd just use the "non mapbox" build types on emulators.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this error is indeed only on architectures with missing libs

Yes the crash occurs only on x86 devices (if I remove those libs).

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
resValue("string", "GOOGLE_MAPS_API_KEY", googleMapsApiKey)
buildConfigField 'String', "MAPBOX_ACCESS_TOKEN", '"' + mapboxToken + '"'
resValue("string", "mapbox_access_token", mapboxAccessToken)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works out of the box. We generate a string named mapbox_access_token with a valid access token and mapbox takes it automatically to setup everything that it needs.

@seadowg
Copy link
Member

seadowg commented May 16, 2022

@grzesiek2010 is the compilation error on CI expected? I'm guessing it's because there isn't a Mapbox API key configured to grab the SDK.

Copy link
Member

@seadowg seadowg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you able to roll back to Kotlin conversions here? It makes it hard to understand the actual diff. For instance, Git is unable to track the changes you had to make to MapboxInitializationFragment d858661.

We should potentially start doing our Kotlin conversions as follow-up PRs as I'm finding it's making reviews (and reviewing my own changes) much harder.

MapView(context).onCreate(null)
}
Configuration.getInstance().userAgentValue = userAgentProvider.userAgent
MapboxUtils.initMapbox()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just don't need this any longer? Definitely nice if so!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don not initialize mapbox like that now. That class was also used to handle cases where a user tried to use mapbox on unsupported devices or a user didn't have a valid access token (then null was returned and we were able to handle that cases). Now as all devices are supported because I removed excluding libraries for X86 devices + without a valid downloads token you won't build the app at all. It's still possible to have a supported device + valid downloads token but wrong access token but it doesn't make sense to check such strange cases I think.

@seadowg
Copy link
Member

seadowg commented May 16, 2022

sharing our tokens so that everyone can build the app including CircleCi
storing Mapbox 10 in our local repository

I think these feel roughly equivalent: both would work but might result in Mapbox suspending our account/revoking keys, and we'd have to resort to doing something else eventually. An alternative to this that still keeps the build simple would be to require CI (which could be leaked easily unless we stop running CI for PRs) and developer to all have their own Mapbox account/API keys and create their own local secrets.properties.

having a separate flavor

I still think something along these lines is my preferred option. We can add a product flavour for when Mapbox is included and when it isn't and have the former include the real implementation of MapboxMapFragment (and other Mapbox classes) and have the latter include a stub one.

As I've thought about it more however, I think there might be a "simpler" way to do achieve the same goals: move Mapbox code to a module (we want to do this anyway for all the different MapFragment implementations) and only include it when we have an access token for the SDK. As an example, in our dependencies for collect_app we'd have something like :

if (mapboxAccessToken != null) {
    implementation project(':mapbox')
}

This would allow us to have a module that would only compile when mapboxAccessToken is present. In other words, it's OK if it doesn't compile because of a missing dependency. Then when we're creating the Fragment in collect_app we can just load the class dynamically rather than statically:

try {
    return this.getClassLoader().loadClass("org.odk.collect.mapbox.MapboxMapFragment").newInstance();
} catch (ClassNotFoundException e) {
     return null;
} catch (IllegalAccessException e) {
     return null;
} catch (InstantiationException e) {
     return null;
}

If we do go down this road (which I'm advocating for), we probably want to look at merging #5139 before this as those changes will be needed to allow MapboxMapFragment to be pulled out to a separate module.

@grzesiek2010
Copy link
Member Author

is the compilation error on CI expected? I'm guessing it's because there isn't a Mapbox API key configured to grab the SDK.

exactly there are no MAPBOX_DOWNLOADS_TOKEN so the app can't be built

@grzesiek2010
Copy link
Member Author

Are you able to roll back to Kotlin conversions here? It makes it hard to understand the actual diff. For instance, Git is unable to track the changes you had to make to MapboxInitializationFragment d858661.

I'm not sorry. Generally I always implement changes and once everything is done I add conversion in a separate commit but not this time sorry my mistake. To help you a bit with that class what I changed was:

and that's it.

@seadowg
Copy link
Member

seadowg commented May 17, 2022

Just a note about something to keep in mind: Chromebooks/Chrome OS devices are pretty likely to have x86. That's a real world example of x86 devices that we didn't really consider the first time we removed Mapbox's x86 libs (and therefore x86 support). I imagine we can get away with not supporting Mapbox on Chromebooks, but we should make sure the release build can start OK on them.

@grzesiek2010
Copy link
Member Author

@seadowg
I moved mapbox code to its own module.
I wanted to try load classes dynamically like you said above: #5141 (comment)

The problem is that we use that new module in more places than only to create MapboxMapFragment like in your example.
I'm going to list those places so that it's clear:

what do you think?

@seadowg
Copy link
Member

seadowg commented Jun 10, 2022

creating MapboxMapConfigurator in https://github.com/getodk/collect/pull/5141/files#diff-3a00df4aebd5600fae250c3807e97417704f10649504cc5f3cad20652bfc2a3fR61 the same as above

Yeah, I think we can just dynamically add the Mapbox source option if the configurator is present.

using MapboxMapConfigurator.DEFAULT_MAP_STYLE in Defaults I can replace it with a raw string mapbox://styles/mapbox/streets-v11 so that mapbox module is not used there

👍

MapBoxInitializationFragment in main_menu this would need to load that fragment dynamically so that if the mapbox module is not included there are no errors. BTW I need to double check if we still need this trick.

Yes that sounds right. You'll have to add the Fragment with a transaction rather than loading it in XML using name.

places where we use classes from the new module to setup dagger:
https://github.com/getodk/collect/pull/5141/files#diff-19e8ed4f040ab190989099eb37cd00c559b474b8e802b01260614bf9b628773bR12 https://github.com/getodk/collect/pull/5141/files#diff-a2f0be8cb9ce6618f33c478f6ae0c69fc5e6eefa5807833ffc3f1f324369c5b3R281 this might be a problem

Ooooft yeah that's a problem. I think what we'll have to do there is give the dependency module a constructor with args, and then grab the constructor dynamically. So the module would look like this:

@Module
class MapboxDependencyModule(
    private val settingsProvider: SettingsProvider,
    private val networkStateProvider: NetworkStateProvider,
    private val referenceLayerRepository: ReferenceLayerRepository
) {
    @Provides
    fun providesSettingsProvider(): SettingsProvider {
        return settingsProvider
    }

    @Provides
    fun providesNetworkStateProvider(): NetworkStateProvider {
        return networkStateProvider
    }

    @Provides
    fun providesReferenceLayerRepository(): ReferenceLayerRepository {
        return referenceLayerRepository
    }
}

I'd been thinking that giving these modules constructors would be nice anyway - it prevents us from having to create Collect... subclasses, so it's just less code overall. You can construct that without a static reference like this in Java:

this.getClassLoader().loadClass("org.odk.collect.mapbox.MapboxDependencyModule").getConstructors()[0].newInstance(
    settingsProvider,
    networkStateProvider,
    referenceLayerRepository
);

Given we're using reflection to load and construct classes in multiple places, it might be a good idea to have a helper object that hides all this work. I could imagine a MapboxClassInstanceCreator that has methods like createDependencyModule and createMapboxInitializationFragment.

@grzesiek2010
Copy link
Member Author

grzesiek2010 commented Jun 10, 2022

I've fixed all steps mentioned above apart from DI. As you know i will be off next two weeks so if this blocks you somehow feel free to take over.

What's left:

  • fixing DI
  • removing x86 libs
  • making sure that everything works together

permissions:testDebug
settings:testDebug
maps:testDebug
mapbox:testDebug
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to be careful to run tests ourselves when making changes to mapbox

* [ObjectProviderHost.getMultiClassProvider].
*/

interface ObjectProvider {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a 35 line mini DI framework. I think there are definitely better solutions than this but let's finish the discussion in #4940 before thinking about that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the mean time, consider adding to the comment to state it was introduced to solve a specific problem with Mapbox and is not intended to be used elsewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still thinking that if the result of #4940 is "keep Dagger" than I'll just replace this with a shared module for the Dagger setup. I'll leave this right now until we've made some decisions there.

// Looks for user swipes. If the user has swiped, move to the appropriate screen.

// For all screens a swipe is left/right of at least .25" and up/down of less than .25" OR left/right of > .5"
int xpixellimit = (int) (ScreenUtils.xdpi(odkView.getContext()) * .25);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few tests were failing here due to odkView being null when we swipe back from the end screen. I'm not sure how we've got away with SwipeHandler not dealing with EmptyView and FormEndView for so long.

public T clickOption(int option) {
clickOnString(option);
return page.assertOnPage();
return clickOnButtonInDialog(option, page);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an issue referenced in this method.

@seadowg seadowg marked this pull request as ready for review June 15, 2022 16:57
@seadowg seadowg requested a review from lognaturel June 15, 2022 16:57
android:exported="false"
tools:node="merge">

<meta-data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find these initializer classes referenced in the Mapbox docs. This meant I had to debug inside Android library code to work out what initializers were being run. @lognaturel are we able to send some feedback to Mapbox about this?

lognaturel
lognaturel previously approved these changes Jun 16, 2022
Copy link
Member

@lognaturel lognaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m delighted to say that now that y’all have done the work, it doesn’t look as scary or complicated as I had feared. I thought I’d want to review this interactively but I’m comfortable with the understanding I got async.

* [ObjectProviderHost.getMultiClassProvider].
*/

interface ObjectProvider {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the mean time, consider adding to the comment to state it was introduced to solve a specific problem with Mapbox and is not intended to be used elsewhere.

@lognaturel lognaturel dismissed seadowg’s stale review June 16, 2022 03:54

You’ve taken this over and taken it out of draft so I assume you’re happy with the whole thing now.

@seadowg seadowg mentioned this pull request Jun 20, 2022
3 tasks
```
**Mapbox Maps SDK for Android**: When the "Mapbox SDK" option is selected in the "User interface" settings, ODK Collect uses the Mapbox SDK for displaying maps in the geospatial widgets (GeoPoint, GeoTrace, and GeoShape). To enable this API:
1. [Create a Mapbox account](https://www.mapbox.com/signup/). Note that signing up with the "Pay-As-You-Go" plan does not require a credit card. Mapbox provides free API usage up to the monthly thresholds documented at [https://www.mapbox.com/pricing](https://www.mapbox.com/pricing). If your usage exceeds these thresholds, you will receive e-mail with instructions on how to add a credit card for payment; services will remain live until the end of the 30-day billing term, after which the account will be deactivated and will require a credit card to reactivate.
Copy link
Member

@seadowg seadowg Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like creating Mapbox accounts with @gmail.com addresses requires a credit card for an ID check (but my own domains seem to work fine) and this blocked @kkrawczyk123 from being able to set up an account. I'm hoping @soldevelo emails will work fine, but if not we might want to create an ODK dev account @lognaturel (probably using @getodk.org). For now, I've sent @kkrawczyk123 a secrets.properties file that with tokens generated in my dev account.

@seadowg
Copy link
Member

seadowg commented Jul 1, 2022

It feels like there's only a couple of minor problems left (assuming everything other than the last two issues posted by @srujner are fixed). I'm thinking we can go ahead and merge this to unblock other PRs and create issues for the remaining problems. Sound good @grzesiek2010? If so, @srujner could you create issues for your last couple of comments?

@grzesiek2010
Copy link
Member Author

I'm not sure I would rather give the QA team some time till Monday at least if there is nothing big then we can file separate issues for everything.

@lognaturel
Copy link
Member

Maybe y’all can check in when @seadowg starts his Monday? QA might have made good progress by then and it’d be great to unblock other map-related things in flight.

@seadowg seadowg dismissed stale reviews from lognaturel and themself July 4, 2022 12:57

Out of date

@seadowg
Copy link
Member

seadowg commented Jul 4, 2022

@grzesiek2010 just dismissed reviews so that I remember to have another look at changes. Just request a review when you're done.

@srujner
Copy link

srujner commented Jul 4, 2022

Tested with Success!
Verified on Androids: 5.1, 10, 12

Verified cases:

  • Issue #3883 is no longer reproducing;
  • Five bugs will be created as separate issues, all minor things;
  • Regression checks for GeoPoint, GeoTrace, GeoShape widgets;
  • Regression checks on external geo widgets;
  • Regression checks on Forms: geojson, gps_accuracy_threshold, map internal-select, placement_map, TestingGeoPointWidgetWithValue, OSM_buildings;
  • Select Map forms;
  • Offline Layers;
  • Placement by tapping, manual mode and automatic mode
  • Google Maps, Mapbox, OSM;
  • Collecting Location in background;
  • View Submissions on map;
  • Sending Submission to Central;
  • Switching between maps Google/OSM/Mapbox
  • Map view with displayed points
  • Screen rotation,
  • Minimize app, using backward button,
  • Light and Dark mode,

@dbemke
Copy link

dbemke commented Jul 4, 2022

Tested with Success!
Verified on Androids: 8.1, 11

Copy link
Member

@seadowg seadowg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like b2cea65 and 34089a4 make changes to SelectionMapFragment, so I'm guessing the issues they address (which I think are #5141 (comment) and #5141 (comment) respectively) are not actually Mapbox specific, and I'd suspect are also existing issues rather than new ones caused by this PR (given that the code being changed isn't Mapbox code). Is that right @grzesiek2010?

If so, I think we should leave those commits out and create issues. Those changes will cause some nasty conflicts with #5164 and the issues being fixed may also have already been fixed by those PRs. Additionally, #5164 introduces a way to test interactions with the BottomSheetBehavior which would be needed to write a test for b2cea65 which is currently has no coverage.

AppInitializer.getInstance(context)
.initializeComponent(MapboxMapsInitializer::class.java)
it.set(KEY_MAPBOX_INITIALIZED, true)
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't work out why this didn't need a try-catch when it was called in MapboxMapFragment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't needed because on a device that does not support mapbox choosing mapbox is not possible so that class MapboxMapFragment was never used. However MapBoxInitializationFragment was always used if there were keys provided (what casued crashes on emulators for example). I improved it in 24af451 and now try-catch is not needed indeed because we check both keys and supported abis.

@grzesiek2010
Copy link
Member Author

34089a4 yes it was an existing issue. the problem was that if we received current location before loading items on the map then other events like zooming to those points were ignored. However it wasn't visible on master at least on my device because somehow the process was a little bit faster (I think the new map fragment loads slower).

b2cea65 here again the code was broken but it worked well somehow and only now the bug is visible

So both issues existed but both of them were let's say hidden.

@seadowg
Copy link
Member

seadowg commented Jul 4, 2022

34089a4 yes it was an existing issue. the problem was that if we received current location before loading items on the map then other events like zooming to those points were ignored. However it wasn't visible on master at least on my device because somehow the process was a little bit faster (I think the new map fragment loads slower).

b2cea65 here again the code was broken but it worked well somehow and only now the bug is visible

So both issues existed but both of them were let's say hidden.

Thanks for the clarification. That makes sense. Can we pull those commits out for now (to a branch so that we don't lose the solutions you came up with), and then we can create issues or @getodk/testers can verify as part of a pass on #5164? That PR reworks SelectionMapFragment and adds a lot of tests to SelectionMapFragment so a rebase on top of these commits is going to be much harder than fixing the issues (and being able to write tests for them) there instead.

@grzesiek2010
Copy link
Member Author

Can we pull those commits out for now

Those are pretty serious issues to me so I wouldn't release v2022.3 without it but if you want to just exclude it to avoid conflicts and then fix soon - I'm ok with it and I can do that.

@grzesiek2010
Copy link
Member Author

@seadowg ok I reverted those two commits and I'm going to file separate issues for them.

@seadowg
Copy link
Member

seadowg commented Jul 5, 2022

Those are pretty serious issues to me so I wouldn't release v2022.3 without it but if you want to just exclude it to avoid conflicts and then fix soon - I'm ok with it and I can do that.

Thanks! Agreed, we need to fix before releasing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update Mapbox SDK

5 participants