Updated answer
For the sake of a background image, you can simply wrap your MaterialApp in the DecoratedBox. This is preferable to the other approach (outlined further below) because that abuses builder
which is intended for other purposes:
A builder for inserting widgets above the Navigator or - when the WidgetsApp.router constructor is used - above the Router but below the other widgets created by the WidgetsApp widget, or for replacing the Navigator/Router entirely.
As MaterialApp
solely configures non-rendered widgets and the DecoratedBox
doesn't rely on any of them, it can simply serve as parent widget to the rest of the app achieving the desired effect.
import 'package:flutter/material.dart';
void main() {
runApp(Example());
}
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage("https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg"),
fit: BoxFit.cover,
),
),
child: MaterialApp(
title: 'Flutter Demo',
initialRoute: '/login',
routes: <String, WidgetBuilder>{
'/login': (BuildContext context) => Login(),
'/home': (BuildContext context) => Home(),
},
),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Home'),
ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Go back')),
]
);
}
}
class Login extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Login'),
ElevatedButton(onPressed: () => Navigator.of(context).pushNamed('/home'), child: const Text('Login')),
]
);
}
}
Previous answer
You may use the builder
field on MaterialApp
to provide a TransitionBuilder function that defines a common wrapper for all routes:
import 'package:flutter/material.dart';
void main() {
runApp(Example());
}
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
builder: (context, child) => DecoratedBox(
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage("https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg"),
fit: BoxFit.cover,
),
),
child: child,
),
initialRoute: '/login',
routes: <String, WidgetBuilder>{
'/login': (BuildContext context) => Login(),
'/home': (BuildContext context) => Home(),
},
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Home'),
ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Go back')),
]
);
}
}
class Login extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Login'),
ElevatedButton(onPressed: () => Navigator.of(context).pushNamed('/home'), child: const Text('Login')),
]
);
}
}
It takes a BuildContext
as well as the currently rendered route as child
as arguments and returns a Widget that is then displayed as the route.
Also since it seemed like there was some confusion with regards to the usage of home
and routes
, here is a snipped from the MaterialApp
docs:
- For the / route, the home property, if non-null, is used.
- Otherwise, the routes table is used, if it has an entry for the route.
- Otherwise, onGenerateRoute is called, if provided. It should return a non-null value for any valid route not handled by home and routes.
- Finally if all else fails onUnknownRoute is called.
While you could use home
and routes
together, I personally thing it's more clear what's going on with routes using only routes
in addition to initialRoute
to indicate which is first (unless it is indeed /
which is the default).