Top 10 reasons why your Flutter app is experiencing OutOfMemory errors

Debasmita Sarkar
6 min readMar 15, 2023

--

Well, well, well! Looks like someone wants to know about the infamous OutOfMemory error. Oh boy! this error is enough to make even the most seasoned Flutter developers break out in a cold sweat and start pulling out their hair.

But worry not, my friend!
I’ll break it down for you in a way that’s both descriptive and easy to understand. And because I’m feeling extra generous today :P , I’ll even throw in some code examples to make it all the more fun.

So buckle up and get ready to learn all about how to keep your Flutter apps from turning into memory-hogging monsters. Trust me, your users will thank you for it!

What is OutOfMemoryError?

OutOfMemory occurs when your Flutter app runs out of memory to allocate. In simpler terms, it means that your app has exhausted all the available memory on the device, and it can no longer allocate any more memory.

Why does it happen?

It happens when your app is trying to allocate more memory than what is available. This can occur due to a variety of reasons, such as:

  • Your app is storing too much data in memory
  • Your app is using too many images or videos
  • Your app is not properly releasing memory when it’s no longer needed

Avoiding Out-of-Memory Errors

1. Use efficient data structures

Using efficient data structures is one of the most fundamental ways to optimize memory utilization in your Flutter app. You can use data structures like lists, sets, and maps to store and manipulate data efficiently.

For example, if you need to store a large number of items in a list, consider using a List<E> instead of a List<dynamic>. This way, you can avoid dynamic type checking at runtime and improve the performance of your app.

//before
// Using List<dynamic>
List<dynamic> myList = [];

//after
// Using List<E>
List<int> myList = [];

2. Use const and final for constants and repeated values

You’re probably using a lot of constants in your app, and that’s great! But did you know that using const and final for constants can help reduce memory usage? That's because only one instance of the object is created and reused throughout the app.

const myText = Text('Hello, world!');
final myTextStyle = TextStyle(fontSize: 16, fontWeight: FontWeight.bold);

It is very common to use padding in any app, you can use the const keyword to create a compile-time constant value.

const double paddingSize = 16.0;
Padding(
padding: EdgeInsets.all(paddingSize),
child: Text('Hello, world!'),
);

3. Use const Constructors

You might have widgets that won’t change during runtime. In that case, using const constructors can help avoid creating unnecessary objects, which in turn saves memory. Here's an example:

const MyWidget({Key key, this.text}) : super(key: key);

final String text;

@override
Widget build(BuildContext context) {
return Text(text);
}

4. Dispose of Unused Resources

Just like how you dispose of your pizza boxes after a party, you should also dispose of unused resources in your app. Unused resources like images and animations can take up memory space and cause memory leaks, leading to a sluggish app.
For example,If you are using a TextEditingController to manage text input, be sure to call its dispose() method when you are done with it. This will free up the memory that it is using and improve your app's performance.

final myController = TextEditingController();
// Use the controller...
myController.dispose();

5. Use Lazy Initialization

Another way to optimize memory utilization is to use lazy initialization. This means that you only create an object when it’s actually needed, instead of creating it upfront. This can help to reduce memory consumption, especially for large objects or data sets.

late MyObject _myObject; // Declare object as late to initialize lazily

void doSomething() {
if (_myObject == null) { // Only initialize object if it hasn't been created yet
_myObject = MyObject();
}
// Use _myObject here
}

6. Use Stateless Widgets Wherever Possible

Stateless widgets are a great way to reduce memory usage in your Flutter app. Since they don’t have any state, they’re lightweight and require less memory than stateful widgets. Use them wherever possible, especially for widgets that don’t require any user interaction.

7. Manage images efficiently

Images are a common source of memory utilization in Flutter apps. To reduce the memory footprint of images, you can minimize their size by compressing them.
If your app uses images from the network, consider using the CachedNetworkImage package. This package provides an easy way to cache images locally, reducing the need to reload them from the network and saving memory in the process.

// Non-optimized code
Image.network('https://example.com/image.jpg');

// Optimized code
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),

8. Optimize the widget tree

One of the most important aspects of optimizing memory utilization in a Flutter app is to optimize the widget tree. A widget tree is essentially a hierarchy of widgets that make up the UI of your app. By minimizing the size of the widget tree and avoiding unnecessary rebuilds, you can significantly reduce the memory usage of your app.

One effective way to optimize the widget tree is to use smaller and more efficient widgets wherever possible. For example, using widgets like Container and SizedBox instead of larger and more complex widgets like Card or ListTile can help reduce the overall size of the widget tree.

Another way to reduce the size of the widget tree is to eliminate unnecessary widgets. For example, if you have a widget that only has one child, you can eliminate the parent widget and directly use the child widget instead. Here’s an example:

// Before
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text('Hello, world!'),
],
),
);
}
}

// After
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello, world!')
);
}
}

In this example, the original MyWidget had a Container with a Column as its child, which in turn had a Text widget as its child. By removing the Container and directly using the Text Widget , we were able to reduce the size of the widget tree and optimize memory usage.

9. Use ListView.builder Instead of ListView

If you have a large list of items that you need to display, use the ListView.builder constructor instead of the ListView constructor. This is because the ListView.builder constructor only creates the items that are visible on the screen, whereas the ListView constructor creates all of the items at once.

// Non-optimized code
ListView(
children: myList.map((item) => Text(item.toString())).toList(),
);

// Optimized code
ListView.builder(
itemCount: myList.length,
itemBuilder: (context, index) => Text(myList[index].toString()),
);

10. Use Isolates to do heavy lifting

Isolates are a way to run Dart code in a separate thread, allowing you to perform heavy tasks without blocking the main thread. This can help optimize memory usage and improve the overall performance of your app.

Here’s an example of how you can use isolates to perform heavy tasks in your Flutter app:

import 'dart:async';
import 'dart:isolate';

class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
String _result = 'Waiting...';

Future<void> _performHeavyTask() async {
final receivePort = ReceivePort();
await Isolate.spawn(_heavyTask, receivePort.sendPort);
final result = await receivePort.first;
setState(() {
_result = result;
});
}

static void _heavyTask(SendPort sendPort) {
// Perform heavy task here...
final result = 'Result of heavy task';
sendPort.send(result);
}

@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: _performHeavyTask,
child: Text('Perform Heavy Task'),
),
Text(_result),
],
);
}
}

Bonus tip:

Use enum instead of String for constants

If you have a constant that has a limited set of values, consider using an enum instead of a String. This is because enum values are stored as integers, which require less memory than String values. Here's an example.

// Non-optimized code
final myConstant = 'red';

// Optimized code
enum Color { red, green, blue }
final myConstant = Color.red;

Tools for checking memory usage:

1. Flutter DevTools — This is a suite of performance and debugging tools that includes a memory profiler. It allows you to visualize memory usage over time, track object allocation, and identify memory leaks.

2. Android Profiler — If you’re developing your Flutter app for Android, you can use the Android Profiler tool to analyze memory usage and identify performance bottlenecks. It allows you to monitor CPU usage, network activity, and memory usage.

Hello everyone!! I am Debasmita. I fell in love with Flutter a long time ago and I am head over heels now. I am a Senior Mobile Developer at Peggy , Co-founder of Flutter Hiring Network and a public speaker. Check out recent updates from me in twitter, linkedIn, youtube, github.
If you like this article please give a 👏 or 50 !! Also share your thoughts on comment section. Cheers!! :)

--

--

Debasmita Sarkar

Senior Mobile Developer @Peggy, Flutter Enthusiast, Tech Savvy, Speaker, Artist