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 regarding 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)
What can I check next?
Addendum
I wonder if I am doing something 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?
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...)