Migrating from GetX: Boosting Flutter App Development Efficiency — Part 2: Routing

Fajrian Aidil Pratama
7 min readMay 21, 2023

Replacing GetX Routing with AutoRoute: Streamlining Navigation in Your Flutter App

In the previous part, we discussed the advantages of migrating from the GetX architecture pattern to the Clean Architecture pattern, which resulted in improved code organization and scalability. Now, let’s dive into another significant aspect of our Flutter app migration journey: replacing GetX routing with AutoRoute.

Routing is a fundamental part of any app, allowing users to navigate between different screens and components seamlessly. While GetX provided a simple and efficient routing mechanism, AutoRoute offers additional features and benefits that enhance the navigation experience. In this section, we will explore how AutoRoute simplifies routing in Flutter apps and discuss its advantages over the GetX routing approach.

Let’s get started!

Installation:

To begin using AutoRoute in your Flutter project, follow these simple steps:

  • Open your project’s pubspec.yaml file.
  • Under the dependencies and dev_dependencies section, add the following line:
dependencies:
auto_route: ^[version]
dev_dependencies:
build_runner: ^[version]
auto_route_generator: ^[version]
  • Replace [version] with the desired version of AutoRoute. It is recommended to use the latest stable version.
  • Save the file and run the following command in your terminal to fetch the dependencies:
flutter pub get

With AutoRoute successfully installed, we can now start leveraging its powerful routing capabilities to replace the GetX routing mechanism in our Flutter app.

Defining Routes with AutoRoute:

In GetX, creating new modules and setting up routing is made easy with the get create page [page_name] command, which handles the generation of the page folder, including its binding, controller, and view files, as well as the routing configuration in the app_pages.dart and app_routes.dart files.

With AutoRoute, the process is slightly different but equally straightforward. Let’s walk through the steps:

  • Start by defining your page using the @RoutePage annotation in your page file. For example:
@RoutePage() 
class LoginPage extends StatelessWidget {
//
}
  • Next, run the build runner command in your terminal to generate the routing mechanism based on your annotations:
flutter packages pub run build_runner build
  • This will generate the necessary files to handle routing for your pages.
  • Now, create a configuration class and annotate it with @AutoRouterConfig. This class allows you to customize various settings for AutoRoute. For example, you can specify how routes are generated using the replaceInRouteName property. In my case, I set it to 'Page,Route’, which means that a page named LoginPage will generate a corresponding route named LoginRoute.

bmpr_router.dart

part 'bmpr_router.gr.dart';
@AutoRouterConfig(
replaceInRouteName: 'Page, Route',
)
class BMPRRouter extends _$BMPRRouter {}
  • Inside the configuration class, override the routes getter, where you will define the list of routes for your app. Here’s an example:
part 'bmpr_router.gr.dart';
@AutoRouterConfig(
replaceInRouteName: 'Page, Route',
)
class BMPRRouter extends _$BMPRRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(
page: SplashRoute.page,
path: '/',
),
];
}
  • In this example, we define the SplashRoute as the initial route for the '/' path.
  • Repeat the process for every new route that you have.
    Replace your MaterialApp with MaterialApp.router

app.dart

class BMPRApp extends StatelessWidget {
final bmprRouter = getIt<BMPRRouter>();
BMPRApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp.router(
title: 'Surat BMPR',
routerDelegate: AutoRouterDelegate(
bmprRouter,
navigatorObservers: () => [
BMPRRouteObserver(),
],
),
routeInformationParser: bmprRouter.defaultRouteParser(),
);
}

With these steps completed, you have successfully defined your routes using AutoRoute. The build runner will generate the necessary routing code based on your annotations, simplifying the process of setting up routing in your Flutter app.

Advantages of Using AutoRoute over GetX for Routing:

While GetX provides a convenient way to handle routing in Flutter applications, AutoRoute offers several advantages that make it a compelling choice for managing routes. Here are some key benefits of using AutoRoute:

  • Type-Safe Routing

AutoRoute generates strongly-typed route classes, which means you can avoid dealing with string-based route names and potential typos. With AutoRoute, your routes are represented as classes, ensuring type safety and enabling IDE code completion and navigation.

In addition to the advantages mentioned above, AutoRoute offers even more powerful features for handling route return values. By specifying the return type in the @RoutePage annotation, you can define the expected return type when navigating to a specific page.

For example, if you have a page that expects a boolean value as its return type, you can annotate it with @RoutePage<bool>. This allows you to navigate to that page and retrieve a boolean value as the result when the page is dismissed or popped from the navigation stack.

var router = getIt<BMPRRouter>();
router.pop(true);

AutoRoute also supports using custom classes as return types. This means that you can define your own result classes, such as MailDetailResult, and annotate the corresponding page with @RoutePage<MailDetailResult>. This enables you to pass complex data or custom result objects between routes with type safety.

mail_detail_result.dart

class MailDetailResult extends Equatable {
final bool isUpdated;
final MailSaveDestination destination;
const MailDetailResult({
this.isUpdated = false,
required this.destination
});
@override
List <Object> get props => [isUpdated, destination];
}

mail_detail_page.dart

if (state is MailSaveSuccess) {
var router = getIt <BMPRRouter>();
var result = MailDetailResult(
isUpdated: true,
destination: MailSaveDestination.draft,
);
router.pop(result);
}

home_page.dart

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Button(
onTap: () async {
var result =
await router.push<MailDetailResult>(MailDetailPage(mail: mail));

/// Check if the mail is updated
if (result != null && result.isUpdate) {
/// Show success snackbar and then
/// Navigate to specific page after
/// mail status is updated based on its
/// save destination.
switch (result.destination) {
case MailSaveDestination.draft:

/// Navigate to draft
}
}
},
),
);
}
}

By leveraging strongly-typed route classes and specifying return types, AutoRoute ensures that your navigation flows are type safe and free from potential errors caused by incorrect return values. This enhances the reliability and maintainability of your Flutter app, making it easier to navigate between pages and handle the returned results.

Furthermore, the IDE code completion and navigation features provided by AutoRoute enable developers to easily explore and navigate through the available routes, reducing the chances of introducing errors due to typos or incorrect route names.

Overall, AutoRoute’s support for strongly-typed route classes and customizable return types adds another layer of safety and convenience to your Flutter app’s routing mechanism.

  • Annotation-Based Configuration

AutoRoute utilizes annotations to define routes, reducing boilerplate code and making the routing setup more concise and readable. By simply annotating your page classes with @RoutePage, AutoRoute automatically generates the necessary routing code.

  • Flexible Routing Configurations

AutoRoute provides flexibility in configuring your routes. You can define nested routes, specify route guards for authentication or authorization, handle route transitions, and more. The @AutoRouterConfigannotation allows you to customize various settings according to your app’s specific needs.

  • Declarative Routing

With AutoRoute, you can define your app’s routes declaratively using a builder syntax. This allows you to express complex route structures in a clear and concise manner, making it easier to manage and maintain your app’s navigation flow.

  • Route Parameters and Arguments

AutoRoute simplifies the passing of parameters and arguments between routes. It automatically generates argument classes for your pages, eliminating the need to manually create and manage them. This ensures type safety and reduces potential bugs related to passing arguments between routes.

In traditional GetX routing, passing arguments or parameters between routes requires manual definition and retrieval. For example, if you want to navigate to a page called MAIL_DETAILand pass a mail object as an argument, you would need to do the following:

Get.to(Routes.MAIL_DETAIL, arguments: {
'mail': mail,
});

Then, in the onInitmethod of the destination page’s controller, you would manually retrieve the argument:

mail.value = Get.arguments['mail'];

On the other hand, with AutoRoute, passing arguments becomes much more streamlined. When you have a page that requires an argument, such as ProfileEntity, you can simply define it as a parameter in the page’s constructor:

@RoutePage()
class ProfilePage extends StatelessWidget {
final ProfileEntity profile;
ProfilePage({Key? key, required this.profile});
}

AutoRoute will automatically generate the necessary route file, including an argument class specifically for the ProfilePage:

/// generated route for
/// [ProfilePage]
class ProfileRoute extends PageRouteInfo<ProfileRouteArgs> {
ProfileRoute({
Key? key,
required ProfileEntity profile,
List<PageRouteInfo>? children,
}) : super(
ProfileRoute.name,
args: ProfileRouteArgs(
key: key,
profile: profile,
),
initialChildren: children,
);
static const String name = 'ProfileRoute';
static const PageInfo<ProfileRouteArgs> page =
PageInfo<ProfileRouteArgs>(name);
}

class ProfileRouteArgs {
const ProfileRouteArgs({
this.key,
required this.profile,
});
final Key? key;
final ProfileEntity profile;
@override
String toString() {
return 'ProfileRouteArgs{key: $key, profile: $profile}';
}
}

You can then navigate to the ProfilePage while passing the profile argument directly:

router.push(ProfileRoute(profile: profile));

AutoRoute takes care of type safety and automatically generates the required argument classes, eliminating the need for manual argument management. This not only improves code clarity but also reduces the likelihood of introducing bugs related to passing arguments between routes.

Overall, AutoRoute simplifies the process of passing parameters and arguments between routes, ensuring type safety and reducing potential errors in your Flutter app’s navigation flow.

  • Code Generation and Efficiency

AutoRoute leverages build-time code generation, which means the routing code is generated during the build process rather than being executed at runtime. This approach results in improved performance and efficiency, as the routing code is already generated and optimized before the app is run.

By leveraging these advantages, AutoRoute enhances the routing capabilities of your Flutter app, providing a more robust and efficient solution compared to the routing offered by GetX.

Conclusion

In conclusion, AutoRoute offers numerous advantages and powerful features compared to GetX routing. The generation of strongly-typed route classes enhances type safety and enables IDE code completion and navigation, minimizing the risk of errors and typos. AutoRoute simplifies the passing of parameters and arguments between routes by automatically generating argument classes, eliminating the need for manual management and reducing potential bugs. Additionally, AutoRoute provides advanced features such as route guards, nested routes, and route transitions.

To explore more about AutoRoute and its capabilities, you can visit the official package documentation here. It provides detailed information on how to leverage AutoRoute’s features to enhance your Flutter app’s routing and navigation.

In the next part, we will dive into the benefits of using GetIt and Injectable for dependency injection, offering an improved approach compared to the GetX architecture.

--

--