Migrating complex Flutter apps to null-safety

nikhilmufc7

Nikhil Singh

Posted on March 16, 2022

Migrating complex Flutter apps to null-safety

Flutter with v2.0 introduced null-safety , one of the most requested features in the community and even though they stressed about it not being a breaking change, if you prefer to use the latest packages and keeping things stable in the long run you would most likely want to use null-safe version of packages and to do that you would want your app to be null-safe as well.
To migrate to null-safety you just need to make this small change in your pubspec.yaml

environment:
sdk: ">=2.12.0 <3.0.0"
Enter fullscreen mode Exit fullscreen mode

As soon as you do this everything in your app would start breaking , from your test-cases to your model classes. Most of the errors would look like this

The parameter 'id' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.
Enter fullscreen mode Exit fullscreen mode

To understand this let us first understand what null-safety actually signifies.

types in your code are non-nullable by default, meaning that variables can’t contain null unless you say they can. With null safety, your runtime null-dereference errors turn into edit-time analysis errors

This is the official definition from the dart website which is pretty much self explanatory. Null-safety helps you catch all the null-type exceptions that occur in production right before you ship your app to the public.

So now that we what and how null-safety actually benefits let us talk about actually migrating the app.

Migration Steps

Before we start with our app we need to first migrate all the dependencies our app uses, to check the packages which are not running on a null-safe version. You can do so by running the following command

dart pub outdated --mode=null-safety
Enter fullscreen mode Exit fullscreen mode

This would indicate all the packages that currently have a null-safe version and can be updated.

We can then use the following command to update all the above packages to a null-safe version

dart pub upgrade --null-safety
Enter fullscreen mode Exit fullscreen mode

Migrating the actual app

With v2.0 Flutter team also introduced a handy way to automatically migrate your project to support null-safety by the following command.

dart migrate
Enter fullscreen mode Exit fullscreen mode

Even though its a handy tool for small projects it didn’t quite work for me due to the complexities of my project and the changes it suggested were most likely error prone and a hacky way to get the job done. Although you can still use this and migrate some part of your code which you are confident about , for the rest we can use the manual way.

Depending on how complex your project is its better to migrate it in a stage wise manner, this is what I followed.

  • Migrating all packages
  • Migrating the app
  • Migrating all the test cases
  • Upgrading your pipeline to support null-safety.

We would of-course also want to do this in a stage wise manner so our live app is in no way impacted by breaking changes we will be introducing with null-safety.

To do this we would first want to add this line at the top of all our files. I couldn’t find any way to automate this process so had to this manually.

// @dart=2.10
Enter fullscreen mode Exit fullscreen mode

This fixed almost all of our compile time errors and allows us to fix each file in isolation.

Migrating the test cases

The most time consuming part of the process for me was to get all the test-cases to work, but as soon as your migrate even a file in your app your respective test-case for that file would also need to be migrated in order to preserve the sanity of your pipeline.

If your app is using Mockoon for mocking in test-cases this is where it gets trickier.

The null-safe version of Mockoon completely changes the way you mock your network classes/ Blocs in test-cases. More info on this here.

So now you have two options either you switch to Mocktail that works the same way as Mockoon did prior to null-safety or your continue to use Mockoon.

I had to choose the hard option and continue with Mockoon due to my infrastructure already supporting that and as Mocktail doesn’t support a lot of things like mockoon did , like the use of ‘any’ keyword in constructors etc.

To continue with Mockoon we need to change the way we mocked data, and a lot of code generations comes into the picture.

Mockito provides a “one size fits all” code-generating solution for packages that use null safety which can generate a mock for any class. To direct Mockito to generate mock classes, we can use the new @GenerateMocks annotation, and import the generated mocks library. Let's look at this with an example

Lets suppose we have to Mock our NetworkManager class
Earlier without null-safety we used to mock it this way

class MockNetworkManager implements Mock extends NetworkManager{}
Enter fullscreen mode Exit fullscreen mode

Now with null-safety we need to do it this way

@GenerateMocks([NetworkManager])
void main() {
  test('test', () {
    var networkManager = MockNetworkManager();
  });
}
Enter fullscreen mode Exit fullscreen mode

We need to depend on build_runner. Add a dependency in the pubspec.yaml file, under dev_dependencies: something like build_runner: ^1.10.0.

After adding build runner we just need to run the build runner to generate a mock network manager file for us, we can do this by

pub run build_runner build
Enter fullscreen mode Exit fullscreen mode

The generated mock file is very similar to what we use while generating model classes and it lies in the same folder structure like the test cases where it is being called.

After running the above command ,this would generate network_manager_test.mocks.dart file for us.
Which can be used the same way like we used to before.

Migrating the pipeline

Now that we have migrated or suppressed most of the errors caused by null-safety we can upgrade our pipeline the same way too.

In our case I would be taking a example of CodeMagic.
You can just simply bump your code-magic configs to support the same flutter version as your app.

For running test cases on the pipeline it would take a tad bit longer since it would generate all the mocks for the files on the fly.

Conclusion

This was a very high level overview of how we can migrate the project to support null-safety and at the same time preserve the sanity of your test-cases. Its a complex process and time consuming so its usually recommended to do it in a phase wise manner as I followed above so you can keep pushing new features while at the same time working toward migrating your complete to a null-safe way. Its definitely a pain but it reaps much bigger rewards in the long run as you can restrict most of your runtime errors and give your users a better overall experience on your platform.

💖 💪 🙅 🚩
nikhilmufc7
Nikhil Singh

Posted on March 16, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related