-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[google_maps_flutter] Fix iOS crash by observing map frame change only once #3426
[google_maps_flutter] Fix iOS crash by observing map frame change only once #3426
Conversation
Thx @guykogus , we are having de same bug here, and fix it with help a lot with our platform stability, so i wish they can release the solution as soon as possible |
Exactly the same happens for us.
Flutter 1.22.5, google_maps_flutter: 1.0.6. For us it happens when user stays on a map for ~30 minutes (app in foreground, screen is on) and we are tracking his position and update his position marker (custom one, so we are re-drawing circle on a map). To simulate it, I just use more or less this piece of code: int a = 0;
Timer.periodic(const Duration(milliseconds: 10), (timer) {
if (_mapController != null && position != null && mounted) {
a++;
double b = a > 50000 ? -0.0003 : 0.0003;
setState(() {
position = Position(
longitude: position.longitude,
latitude: position.latitude + b);
});
_mapController.animateCamera(CameraUpdate.newLatLng(
LatLng(position.latitude, position.longitude)));
}
}); I can confirm changes from this pull seems to resolves the problem. Beside, it should be released with version lower than 2.0.0 because plenty of projects depends on more libraries than only google maps and won't be migrated to flutter 2.0.0 for a while (dependency hell 😢 ) |
Unfortunately there's no branch in the original repo to merge with to keep it at version < 2.0 |
I'm aware, but the fix won't be usable for old projects that have dozens of non flutter / dart team libraries dependencies. Until then, guess we will use your fork:
|
You can use |
We have the same problem. Will try this branch to see if it fixes the issue. The app that we're building is heavily using maps. And hasn't been stable since this bug keeps the app crashing |
Hi! We have the same problem. We are using a fork with this changes and fixed the crash. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for providing the fix, the change looks good.
It's not clear to me in what scenario the view
is called multiple times for a single webview.
It looks like it's only called once per controller, see: https://github.com/flutter/engine/blob/master/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm#L161
Again, this is a good fix nonetheless, the old code is pretty confusing and didn't do what it says it wants to do.
However, we should add some tests before landing this. I wonder If you know a way to reproduce this consistently.
I couldn't come up with one when I made this fix. It happens in our project, and clearly it does for others too. Do any of you have a simple sample test that can be provided here, @pawlowskim @sangTroo @carlosmobile ? |
Any update when this fix will be merged? |
Hi, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this should be easily testable with a native unit test:
- Allow injecting a mock
GMSMapView
- Call
view
several times - Ensure that
addObserver:...
is not called on the mock repeatedly.
@stuartmorgan I've been playing a bit with doing something like that but I don't really know how to, at least not without breaking the current code structure/API. Any extra guide would be greatly appreciated. |
Can you elaborate on the issue you are having? If it's the mock injection, for something that's created in init like the map view you generally add a new init method with the thing you want to inject added, which becomes the main initializer, and the real one delegates to that. E.g.:
You expose the new init method in a |
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
@guykogus Are you planning on adding tests per #3426 (review) so that this can move forward? |
@stuartmorgan yes, I really want to because this change is essential, I just haven't had a chance to do it... |
@stuartmorgan I've added some tests, but I'm not sure if I've really done it correctly. Basically I just tested to see if it doesn't lag a long time to loop over |
@@ -2,6 +2,7 @@ | |||
|
|||
* Removes dependencies from `pubspec.yaml` that are only needed in `example/pubspec.yaml` | |||
* Updates Android compileSdkVersion to 31. | |||
* Fix iOS crash on `EXC_BAD_ACCESS KERN_PROTECTION_FAILURE` if the map frame changes long after creation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes
(Per the repo CHANGELOG style guide.)
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface MockRegistrar : NSObject <FlutterPluginRegistrar> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you mocking these manually instead of with OCMock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've never used OCMock. Thanks for the tip!
} | ||
[[controller view] setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 0, 0)] forKey:@"frame"]; | ||
|
||
[viewExpectation fulfill]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this test is supposed to be doing. It's not asserting what this PR is actually intended to fix.
I described what a test should look like above; I'm not sure why you've done something completely different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. Got distracted with seeing if I could actually test for the crash itself to happen. I'll update it.
@stuartmorgan I've updated according to your suggestions, but I'm not sure about the placement of GoogleMapController-Test.h. I added it into the example project itself, because otherwise it would be exported with the pod. Is that correct, or is there some way in the podspec to specify target test files? |
It should live next to the source file; you can use a module map to control how it's exposed. See #4430 for an example of setting that up. |
@@ -31,6 +31,8 @@ target 'Runner' do | |||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) | |||
target 'RunnerTests' do | |||
inherit! :search_paths | |||
|
|||
pod 'OCMock' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have a version. See other uses for examples.
|
||
@interface FLTGoogleMapController (Test) | ||
|
||
- (instancetype)initWithMapView:(GMSMapView *)mapView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All methods need declaration comments, per the ObjC style guide.
packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
Show resolved
Hide resolved
|
||
@import GoogleMaps; | ||
|
||
@interface MockGMSMapView : GMSMapView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a declaration comment.
|
||
@interface MockGMSMapView : GMSMapView | ||
|
||
@property(nonatomic, assign, readonly) NSInteger frameObserverCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a declaration comment.
|
||
@import GoogleMaps; | ||
|
||
@interface MockGMSMapView : GMSMapView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should probably call this PartiallyMockedMapView
or something along those lines; the assumption for a mock object is not that it is the real object but with some overridden behavior, but that it has no behavior other than what it is given in test setup.
@@ -3,6 +3,7 @@ | |||
* Removes dependencies from `pubspec.yaml` that are only needed in `example/pubspec.yaml` | |||
* Updates Android compileSdkVersion to 31. | |||
* Internal code cleanup for stricter analysis options. | |||
* Fixes iOS crash on `EXC_BAD_ACCESS KERN_PROTECTION_FAILURE` if the map frame changes long after creation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't add changes to a version that has already been published. It needs a new version.
|
||
@import GoogleMaps; | ||
|
||
// Defines a map view used for testing key-value observing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use Doxygen style: https://google.github.io/styleguide/objcguide.html#declaration-comments.
// Defines a map view used for testing key-value observing. | ||
@interface PartiallyMockedMapView : GMSMapView | ||
|
||
/// Counts the number of times that `frame` KVO has been added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same. You're also missing the period; comments should be properly punctuated, per the style guide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this is a property, so should describe a noun rather than a verb; methods do things, properties are things. I.e., "The number of times that [...]".
@interface FLTGoogleMapController (Test) | ||
|
||
/** | ||
* Initialize a map controller with a concrete map view. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Initializes
(Per style guide.)
packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter-umbrella.h
Show resolved
Hide resolved
@stuartmorgan done |
There's a format error, please run:
|
@jmagman done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
I cannot to tell you the stress this bug has caused me 😅
The
view
method gets called a lot. Every draw cycle, in fact. And if the frame should change, for example when the keyboard appears, every single one of thoseobserveValueForKeyPath:
observations will be called, which crashes an app with the following exception:EXC_BAD_ACCESS KERN_PROTECTION_FAILURE
Pre-launch Checklist
[shared_preferences]
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.