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).