'Flutter iOS crash with EXC_BAD_ACCESS error
I am developing an app with flutter and testing the app on a physical iOS device (Iphone 7). The iOS version is: 15.3.1 And the flutter version is: 2.10.3
When I am testing my app, I occasionally get crashes. The crash gives the error below. It does not always crash at the same place so I do not know what code to share here. The error message itself does not say much to me and I could not find any helpful information on the net wrt this error.
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x443b8000443b8000)
frame #0: 0x0000000198405048 libobjc.A.dylib`objc_msgSend + 8
libobjc.A.dylib`objc_msgSend:
-> 0x198405048 <+8>: ldr x13, [x0]
0x19840504c <+12>: and x16, x13, #0xffffffff8
0x198405050 <+16>: mov x15, x16
0x198405054 <+20>: ldr x11, [x16, #0x10]
Target 0: (Runner) stopped.
Lost connection to device.
Exited (sigterm)
I am totally clueless of what to check after many tries for several days. Would anyone with more experience in this field be able to help me?
ADDENDUM:
I wonder if I am doing sth wrong with my Stream Builders. Here is a shortened version of my code:
class PrepareList extends StatefulWidget {
final String place;
const PrepareList({
Key? key,
required this.place,
}) : super(key: key);
@override
State<PrepareList> createState() =>
_PrepareListState();
}
class _PrepareListState
extends State<PrepareList> {
late final Stream? _listStream;
@override
void initState() {
super.initState();
String dbChild = "events/" + widget.place + "/";
final db = FirebaseDatabase.instance
.ref()
.child(dbChild)
.orderByKey()
.limitToLast(1500);
_listStream = db.onValue;
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _listStream,
builder: (context, AsyncSnapshot<dynamic> dbEvent) {
if (dbEvent.hasError) {
return CircularProgressIndicator();
}
else if (dbEvent.hasData) {
DataSnapshot dataSnapshot = dbEvent.data!.snapshot;
List<EventDetails> placeEventList = [];
if (dataSnapshot.value != null) {
(dataSnapshot.value as Map<dynamic, dynamic>)
.forEach((key, value) {
placeEventList.add(EventDetails.fromRTDB(value));
});
placeEventList
.sort((a, b) => a.dateEST.compareTo(b.dateEST));
return AttendeeList(placeEventList: placeEventList, place: place);
} else {
return PlaceDataNotAvailable(place: widget.place);
}
} else {
return CircularProgressIndicator();
}
});
}
}
class AttendeeList extends StatefulWidget {
final List<EventDetails> placeEventList;
final String place;
const AttendeeList({
Key? key,
required this.placeEventList,
required this.place,
}) : super(key: key);
@override
State<AttendeeList> createState() =>
_AttendeeListState();
}
class _AttendeeListState
extends State<AttendeeList> {
late final Stream? _attendeeListStream;
@override
void initState() {
super.initState();
String dbChild = "attendees/" + widget.place + "/";
final db = FirebaseDatabase.instance
.ref()
.child(dbChild)
.orderByKey()
.limitToLast(1500);
_listStream = db.onValue;
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _attendeeListStream,
builder: (context, AsyncSnapshot<dynamic> dbEvent) {
if (dbEvent.hasError) {
return CircularProgressIndicator();
}
else if (dbEvent.hasData) {
DataSnapshot dataSnapshot = dbEvent.data!.snapshot;
List<AttendeeDetails> attendeeList = [];
if (dataSnapshot.value != null) {
(dataSnapshot.value as Map<dynamic, dynamic>)
.forEach((key, value) {
attendeeList.add(EventDetails.fromRTDB(value));
});
attendeeList
.sort((a, b) => a.dateEST.compareTo(b.dateEST));
return Scaffold(
body: ShowLists(placeEventList, attendeeList);
} else {
return PlaceDataNotAvailable(place: widget.place);
}
} else {
return CircularProgressIndicator();
}
});
}
}
Widgets above can be called multiple times in the lifecycle of the app. The user comes to this screen by selecting a place on the initial screen, which executes the code in the PrepareList stateful widget, which in its turn calls the AttendeeList Stateful widget.
I would like to emphisize that both PrepareList and AttendeeList use streams. And each time the code in these widgets is executed, a large number of nodes (1500 for each widget) are downloaded from database.
One execution can look as the following:
PrepareList("London");
And another execution may look as the following, presenting a new list of items on the same screen:
PrepareList("Manhattan");
And what I observe is:
When I run PrepareList("London"); for the first time, it takes some time to (3 to 4 seconds) to see the content on the screen. Then I run the PrepareList("Manhattan");, which also takes around 3 to 4 seconds to show the content. But when I run PrepareList("London"); again, the content appears on the screen very quickly, in ~1 second.
In order to be able to call PrepareList(), I need to go to another screen, which means - in my understanding - that my stream subscription is cancelled each time I leave the screen associated to 2 widgets above. But is it so that the stream itself is NOT cancelled and the data remains in the memory?
What I suspect:
When using the app, as I call PrepareList(...) for multiple places (multiple times), it loads more and more data on the memory and it never cleans it. After a while, the app consumes all available memory and crashes by giving the error above, which tells me nothing meaningful.
And as PrepareList(...) is executed more and more when using the app, the Iphone 7 gets heated, which I can easily feel. I even tested with Iphone 12, which does not get heated as Iphone 7 but crashes as well.
I even tried to add dispose as following to both classes:
@override
void dispose() {
super.dispose();
}
... but it still did not help.
Is my stream implementation true? Is what I suspect can be the underlying problem for this crash?
Any help would very highly be appreciated!
ADDENDUM 2:
I kept trying...
I used the app in a way so that PrepareList("..."); is triggered several times. I observed the memory usage in devtools as well. I can observe that the memory usage increases over time. And I got a new error this time saying something more concrete:
[ServicesDaemonManager] interruptionHandler is called. -[FontServicesDaemonManager connection]_block_invoke
[tcp] tcp_input [C17.1.1:3] flags=[R] seq=3749683210, ack=0, win=0 state=LAST_ACK rcv_nxt=3749683210, snd_una=3584722489
[tcp] tcp_input [C17.1.1:3] flags=[R] seq=3749683210, ack=0, win=0 state=CLOSED rcv_nxt=3749683210, snd_una=3584722489
* thread #46, name = 'DartWorker', stop reason = EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=1450 MB, unused=0x0)
frame #0: 0x0000000108ef4d0c Flutter`dart::CompilerPass_TypePropagation::DoBody(dart::CompilerPassState*) const + 1644
Flutter`dart::CompilerPass_TypePropagation::DoBody:
-> 0x108ef4d0c <+1644>: str xzr, [x20, x28, lsl #3]
0x108ef4d10 <+1648>: ldr x8, [x22, #0x48]
0x108ef4d14 <+1652>: cmp x24, x8
0x108ef4d18 <+1656>: b.ge 0x108ef4d84 ; <+1764>
Target 0: (Runner) stopped.
Lost connection to device.
This time, it says EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=1450 MB, unused=0x0). Seemingly, the memory usage is increasing over time. Why is the memory not freed when the user leaves the screen on the phone? (I ask this question by suspecting that the stream still occupies the memory even after the user moves to another screen...)
Solution 1:[1]
A couple thoughts for the permanently increasing memory usage because it is practically impossible to answer your question without having a full example.
Firebase caches the objects, thats why it is faster, the 2nd time. This should not effect your memory usage by an amount that breaks the app.
-
I ask this question by suspecting that the stream still occupies the memory even after the user moves to another screen...
Maybe you are using Navigator.push* and never pop any of the previous
routes, all the lists in the previous routes are being kept in memory.
Track the dispose calls, see if the widgets get cleaned up. By using logging, breakpoints or even better track the widget counts by using the DevTools memory view: https://docs.flutter.dev/development/tools/devtools/memory The memory snapshots can tell you exactly where your memory is being used.
In general it is better to move the sorting out of the
buildfunction. But this is more of a performance hint than a problem.Your memory problems may be unrelated to the
EXC_BAD_ACCESScrash but you should try to solve them first. It will make it easier to find the underlying cause, if there is any.
Solution 2:[2]
EXC_BAD_ACCESS error is really frustrating and iOS device crashes occur randomly on "Debug mode". I am using "Release Mode" when I test my app on iOS device. Crash report can not help, it is not pointing the issue exactly. I am developing my app with Android device and when I finish with some parts/modules of my app, I see the output in "Release Mode" on iOS device. Use this command on Terminal, to build your app in "Release Mode" if you are working with iOS device:
flutter run --release
Solution 3:[3]
I am aware that my question was not truly organized and I could not provide a proper code that replicated the problem. After many tries, I seemingly solved the problem. As it only happens sometimes, the picture is still blurry to me but I at least know what solved my problem.
It is yet still difficult to provide a complete code but I will try to explain what solved my problem with some pseudo code here, which I hope may be helpful to others who may suffer from a similar problem.
No matter how unhappy I am with it, I have some inevitable nested streams in some parts of the app. On one page, I have multiple nested stream builders but I will illustrate the situation with 2 streams only below:
StreamBuilder1
--> Read data A from database
StreamBuilder2
---> Read data B from database (by using data from data A)
When reading data B from database, some input needs to be used from StreamBuilder1. There is a dependency.
These 2 streams occasionally causes the page to refresh. One of the reasons that may cause EXC_BAD_ACCESSS seems to be trying to read a part of memory that is already freed. What I "suspect" is that data A is occasionally not really there in the memory when StreamBuilder2 needs to use it to read its own data from the DB.
I made the following change...
StreamBuilder1
--> Read data A from database
FutureBuilder2
---> Read data B from database (by using data from data A)
I replaced StreamBuilder2 with a FutureBuilder, and unexpected crashes disappeared.
I essentially had multiple nested stream builders. That would be great to have streams for all of them but it is not really a must. Only one of the data feeds needed to be implemented with a stream builder, which I did. And I replaced all other streams with future builder instead. Since then, I have not had the EXC_BAD_ACCESS crash.
I may be misinterpreting how things work or how this change solved my problem. But this is at least what I did and what really solved my problem at the end. Would anyone have any further comments, s/he is more than welcome to share them as I am very interested in learning what things I need to consider when using nested streams as it is a bit difficult to find good examples out there.
Side note: I even studied BLoC a little bit, hoping that it could help to solve this problem. I guess it is easy for people who already know how to use it. But it is quite daunting, especially if you want to use it for multiple streams when reading data from firebase, and I could not find any example of how to do it with BLoC.
Solution 4:[4]
instead of having a link to forward to, you need to add mailto: followed by your email like this
"mailto:[email protected]"
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | kuhnroyal |
| Solution 2 | CanDroid |
| Solution 3 | |
| Solution 4 | Mohamed Salah |
