Flutter iOS crash with EXC_BAD_ACCESS error
Asked Answered
S

7

14

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...)

Sible answered 7/3, 2022 at 14:52 Comment(12)
investigating similar issue on Flutter v2.16.1, ios v15.1. Are you using Firebase DB? I've noticed a Firebase query shortly before the error occurs on my end?Unrealizable
@Unrealizable Thanks for sharing your comment. Yes I do use Firebase. It is though impossible to locate where it is happening because it may crash at totally different irrelevant places of the app. That would be great to understand what this error means (e.g. does that mean insufficient memory?) so that I will have an idea about what to look at.Sible
You won't get an answer like this. You need to provide a lot more info on plugins and probably the full crash log from the device.Snatchy
@Snatchy How do I get the full crash log from the device?Sible
@Sible developer.apple.com/documentation/xcode/…Snatchy
@Snatchy I added the crash log from the phone now. Does it tell you anything? Please note that the question exceeded the total number of character limit so I provided the remaining part of the crash in an answer below.Sible
@Snatchy I added even more information to my question above. Any chance you can look at it?Sible
@RémiRousselet Any chance you can look at this question?Sible
EXC_BAD_ACCESS happens when objective C is trying to use an object that has already been freed. It could mean that there are too many UI widgets in memory at the same time? Are you using ListView.builder() or equivalent that recycles UI widgets? Maybe it's a good idea to separate the code where you're getting data from firebase from the code that is listing the data? Maybe you can try to build this to android or android emulator and see if you get a different error? Or maybe build it to web?Antifouling
@Antifouling Thank you for your comment! Yes,I am using ListView.builder in parts of the code and the code that gets the data from Firebase is not necessarily separated from the code that lists the data. An yes, there are quite a number of UI widgets. I will start with trying to separate the Firebase implementation from UI implementation. EXC_BAD_ACCESS only happens occasionally at different part of the UI (not necessarily on the same page), which makes it extremely hard to localize where I should be focusing on. Any other comment or explanation more than welcome!!Sible
Hi @Antifouling did you fixed that issue? because I have the same problem as you.Panatella
@NhậtTrần No, I did not. The problem only occurs on iOS devices while things seem to be working perfectly fine on Android. And I have no clue of what possibly is causing the problem.Sible
B
3

In my case it was causing due to connectivity_plus

  • I just updated it to 5.xx
  • pod deintegrate
  • pod install
Bremble answered 25/10, 2023 at 10:32 Comment(1)
yup here's the thread github.com/fluttercommunity/plus_plugins/issues/2162Dorcy
S
1

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.


Addendum (2022 June 24): I still have this problem that occasionally is occurring. It has not happened yet in Android but the app gets closed for an unknown reason. It does not happen that often after the changes I mentioned above but the problem has not totally disappeared yet.

Sible answered 17/3, 2022 at 20:3 Comment(0)
S
1

A couple thoughts for the permanently increasing memory usage because it is practically impossible to answer your question without having a full example.

  1. 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.

  2. 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.

  1. 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.

  2. In general it is better to move the sorting out of the build function. But this is more of a performance hint than a problem.

  3. Your memory problems may be unrelated to the EXC_BAD_ACCESS crash but you should try to solve them first. It will make it easier to find the underlying cause, if there is any.

Snatchy answered 21/3, 2022 at 12:16 Comment(1)
Thank you very much for your comments. I did some of them and it was truly helpful. My original problem with EXC_BAD_ACCESS appeared to be related to another problem which I will explain in a separate answer.Sible
A
0

In my case its crashing due to firebase push notification. I removed notification from the notification payload and its works:

{
    // "notification":{"title": "title","body": "body" }, DON'T USE THIS LINE EVER!
    "to": "token",
    "priority": "high",
    "data": {
      "click_action": "FLUTTER_NOTIFICATION_CLICK",
      "content": {
        "payload":{
          "type": "Message",
          "userId": "123",
          "postId": "postId"
        },
        "id": 100,
        "channelKey": "basic_channel",
        "body": "body",
        "title": "title"
      }
    },
    "mutable_content" : true,
    "content_available": true
  }
Autum answered 12/9, 2022 at 15:37 Comment(1)
Thanks for your reply. It is not related to push notifications in my case because it crashes even when there is no push notification being received.Sible
H
0

I had a similar error (EXC_BAD_ACCESS). In my case, it was a meaningless (not sure that not correct) usage of late keyword. (screenshot) Probably, I had copypaste it from another place... And neither the analyzer nor the compiler warned about the error. Only platform level crash logs. Besides, flutter clean helped, but after some time error firing again.

enter image description here

Heraclea answered 31/7, 2023 at 7:45 Comment(0)
L
0

what causes the error for me is using CustomClipper I think this is related to impeller on ios while this works well in Android.

class VerticalClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final Path path = Path();
    path.moveTo(-double.maxFinite, 0);
    path.lineTo(double.maxFinite, 0);
    path.lineTo(double.maxFinite, size.height);
    path.lineTo(-double.maxFinite, size.height);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return false;
  }
}
Leukocyte answered 27/12, 2023 at 20:38 Comment(0)
B
0

In my case it was crashing with the same error due to rendering high resolution images:

So I used "property cacheWidth or cacheHeight", something like this:

Image.network(https://i.redd.it/zm4nfgq29yi91.jpg", cacheWidth: 360,)

This solution worked for me.

Bronwyn answered 5/3, 2024 at 13:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.