Hello, Welcome back. Glad that you’re here.
We're building a Movie App with the best coding practices and tools. In the previous tutorial, we worked on adding a local database to store favorite movies and user preferred language.
In this tutorial, we will not add any further screens as mostly all our primary features are done. From now on, we will work on features that convert an average app to a better app. Where you care about the UI feedback engaging users for a longer time and providing fun time while using the application. We can give the user a better experience by writing code that can help in scaling the app quickly with features. Another way is to give proper feedback on what is happening in the application, like showing loaders while we make an API call, etc. One more way would be to keep the plugins updated and grab most of their features.
Let's get started.
Current Implementation
We are using Navigator.of(context).push(<Widget>);
to navigate between screens. This seems easy and straightforward. But, in this way we are duplicating our code like whenever you tap on any movie card, you have to navigate to the movie detail screen. There are 4 scenarios in the app currently. In the future, as and when we add more features, the instance of the code will increase.
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MovieDetailScreen(
movieDetailArguments: MovieDetailArguments(movie.id),
),
),
);
Clearly, MovieDetailScreen
uses only movieDetailArguments
now, but when you want to add or remove any such parameters from MovieDetailScreen
you'll have to change it in all the places. So, to manage this properly and have a maintainable way of navigating to screens, let's try to keep this in one place.
Routes
First and foremost is to create a file to collect all the possible routes in the app and put their path in the constants file. Create a route_constants.dart file in constants folder:
//1
class RouteList {
RouteList._();
//2
static const String initial = "/";
static const String movieDetail = "/movie-detail";
static const String watchTrailer = "/watch-trailer";
static const String favorite = "/favorite";
}
- Create a class
RouteList
and give it a non-initializable constructor. - Next, you'll write routes. Starting with the initial route i.e. home which is indicated by forward-slash(/) and then all the other routes with unique paths for movie detail screen, watch trailer screen, and favorite screen. We don't need for search screen because that we handle it via an inbuilt call to search screen using
showSearch()
.
Pass Initial Route
Now that we are ready with the initial
route, let's make sure that we add that in the MaterialApp
. Open movie_app.dart:
//1
builder: (context, child) {
return child;
},
//2
initialRoute: RouteList.initial,
- Remove the
home
parameter and instead pass thebuilder
which will return in the dynamicchild
widget ofMaterialApp
. This dynamicchild
widget will be generated by aRouteFactory
that we will create after this. - Next, tell the
MaterialApp
what route it has to consider as theinitialRoute
. We will create a mapping of the routes with widgets inRouteFactory
itself.
Route Factory
There are 2 main things left to implement navigation, first create a route factory and then generate a route using it. Let's first create the RouteFactory
.
Create a file routes in the presentation folder:
class Routes {
//1
static Map<String, WidgetBuilder> getRoutes(RouteSettings settings) => {
RouteList.initial: (context) => HomeScreen(),
RouteList.movieDetail: (context) => MovieDetailScreen(
movieDetailArguments: settings.arguments,
),
RouteList.watchVideo: (context) => WatchVideoScreen(
watchVideoArguments: settings.arguments,
),
RouteList.favorite: (context) => FavoriteScreen(),
};
}
- Create a class
Routes
and make a static methodgetRoutes()
. This method will return aMap
ofString
andWidgetBuilder
, wherekey
is the route path andvalue
is a function that returns a widget or screens. For those screens that require arguments likeMovieDetailScreen
, pass the arguments fromRouteSettings
. You will be better able to relate when we call thepushNamed()
.
Generate Route
Now, we will write code where the real magic happens. Based on the route, we will return the widget with arguments whenever necessary. Open movie_app.dart and add onGenerateRoute
property of MaterialApp
.
//1
onGenerateRoute: (RouteSettings settings) {
//2
final routes = Routes.getRoutes(settings);
//3
WidgetBuilder builder = routes[settings.name];
//4
return FadePageRouteBuilder(
builder: builder,
settings: settings,
);
},
- The
onGenerateRoute
property takes in a method with theRouteSettings
type field. TheRouteSettings
type class consists of 2 things - the name of the route and arguments attached to the route. Both are which are useful to us. - Next, using these
settings
get the Routes from the map we created before. - Next, get the
WidgetBuilder
by the pathname from the map. - Last, pass the builder and setting to
FadePageRouteBuilder
. This customer builder will also be useful to add some transitions while navigating between screens. Let's create that too. Although, I have already shown this many times in my videos.
FadePageRouteBuilder
Create a new file fade_page_route_builder.dart in the presentation folder:
//1
class FadePageRouteBuilder<T> extends PageRouteBuilder<T> {
//2
final WidgetBuilder builder;
final RouteSettings settings;
FadePageRouteBuilder({
@required this.builder,
@required this.settings,
}) : super(
//3
pageBuilder: (context, animation, secondaryAnimation) =>
builder(context),
//4
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
var curve = Curves.ease;
var tween =
Tween(begin: 0.0, end: 1.0).chain(CurveTween(curve: curve));
//5
return FadeTransition(
opacity: animation.drive(tween),
child: child,
);
},
//6
transitionDuration: const Duration(milliseconds: 500),
settings: settings,
);
}
- Create a class that extends
PageRouteBuilder
. - Define the 2 fields that are required -
WidgetBuilder
andRouteSettings
. - In the
super()
, return thebuilder
itself. - For transitions between the widgets, use the
transitionBuilder
and defineCurve
andTween
as per the animation you required. We are going with a faded transition. - Now, return the
Fadetransition
with animated opacity. - Lastly, define the duration for the transition to take place. And, pass the
RouteSettings
as well.
Use Navigator.pushNamed()
And at last, we have come to a place where we need to use the pushNamed()
instead of push()
.
//1
Navigator.of(context).pushNamed(RouteList.favorite);
//2
Navigator.of(context).pushNamed(
RouteList.movieDetail,
arguments: MovieDetailArguments(movieId),
);
//3
Navigator.of(context).pushNamed(
RouteList.watchTrailer,
arguments: WatchVideoArguments(_videos),
);
- Use the
RouteList.favorite
to open your favorite movies screen. - Use the
RouteList.movieDetail
to open the movie detail screen with arguments. - In a similar way, use the
RouteList.watchTrailer
to open the trailer screen.
Run the app and do a sanity check of whether you can navigate to all screens of the screen or not. We also have a Fade effect now while navigating to the screens.
By this, we have come to an end for this tutorial. In the next tutorial, we will work on adding loader to the API calls. See you in the next tutorial. Thanks for reading.