How to expose provider to screen. (Provider to expose only available on previous screen) - flutter

I have 2 providers, Product and Products.
3 screens and 1 widget, a ProductsScreen that list all products with a ListView.builder with the widget as its child, a ProductDetailScreen that shows a single product details and a ProductEditScreen to edit a product.
I expose the Products provider on main, and the Product provider to each item in the ListView.
The user can go to ProductEditScreen on ProductsScreen and on ProductDetailScreen.
With the code I have now if user goes to edit on the ProductsScreen, edits the product and saves, the app goes back to ProductsScreen and the edited product shows the correct data.
I'm having a difficult time figuring out how to expose the Product provider to ProductDetailScreen. I need the product provider also here because if user goes to product edit and saves, the app goes back I need to show the correct product data.
main.dart
...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Products(),
),
],
child: MaterialApp(
title: 'My app',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
),
initialRoute: ProductsScreen.routeName,
routes: {
ProductsScreen.routeName: (ctx) => ProductsScreen(),
ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
ProductEditScreen.routeName: (ctx) => ProductEditScreen(),
},
),
);
}
}
products_screen.dart
...
class ProductsScreen extends StatelessWidget {
static const routeName = '/products-screen';
#override
Widget build(BuildContext context) {
final products = Provider.of<Products>(context, listen: true);
return Scaffold(
appBar: AppBar(
title: Text('Products'),
),
body: ListView.builder(
itemCount: products.items.length,
itemBuilder: (ctx, idx) => ChangeNotifierProvider.value(
value: products.items[idx],
child: ProductItemWidget(),
),
),
);
}
}
product_item_widget.dart
...
class ProductItemWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context);
return Card(
child: ListTile(
leading: product.image.isEmpty
? Icon(Icons.image)
: Image.asset(product.image),
title: Text(product.name),
subtitle: Text(product.info),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.of(context).pushNamed(
ProductEditScreen.routeName,
);
},
),
],
),
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
);
},
),
);
}
}

Related

Flutter common background image for all routes

I am trying to add a background image which is always there no matter which route is active. I the example below is inspired by this answer but the background will only be visible for the route "/". I was hoping to not have to set the background image for each route. Any suggestions?
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/camping-background.png"),
fit: BoxFit.cover),
),
routes: <String, WidgetBuilder>{
'/login': (BuildContext context) => const Login(),
'/register': (BuildContext context) => const Register(),
'/home': (BuildContext context) => const Home(),
},
);
}
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).
Copy & Paste it on dartpad to see the results
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class BaseLayout extends StatelessWidget {
final Widget? child;
const BaseLayout({Key? key, #required this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
width: 720,
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage(
"https://images.pexels.com/photos/440731/pexels-photo-440731.jpeg"),
fit: BoxFit.fill),
),
child: child,
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Home')),
body: BaseLayout(child: Home()),
),
routes: <String, WidgetBuilder>{
'/login': (BuildContext context) => Login(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
},
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
const Text('HOMEPAGE', style: TextStyle(fontSize: 32)),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('Login'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(title: const Text('Login')),
body: BaseLayout(child: Login())),
),
);
},
),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('Register'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BaseLayout(child: Register())),
);
},
),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('No Background Image Screen'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NoBackground()),
);
},
),
],
);
}
}
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Login',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('Back to Homepage'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
}
}
class Register extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'Register!',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('Back to Homepage'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
}
}
class NoBackground extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
const Text(
'No Background Image!',
style: TextStyle(color: Colors.white),
),
const SizedBox(height: 12),
ElevatedButton(
child: const Text('Back to Homepage'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
}
}
Complete with scaffold app bar & no background image sample
Check it on Gist: Complete code here

GoRouter - Can I push 2 pages at once?

I'm using go_router and I am about to do this in a callback of one of my buttons:
EvelatedButton(
onPressed: () {
GoRouter.of(context)
..push('/page-1')
..push('/page-2');
},
)
This is to push 2 pages in the history at once. After the user click on this button, he ends up on the page page-2 and when he pops the page, there is page-1.
Is it acceptable to do that or is there any reason not to do it?
What would be those reasons and what should I do instead?
I don't think I've seen anything like that in go_router's examples.
For more context, here is a code snippet (or checkout https://github.com/ValentinVignal/flutter_app_stable/tree/go-router/push-twice-at-once):
When the button is pressed, I want to display the dialog page with the page-1 in the background.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
initialLocation: '/page-0',
routes: [
GoRoute(
path: '/page-0',
builder: (_, __) => const Page0Screen(),
),
GoRoute(
path: '/page-1',
builder: (_, __) => const Page1Screen(),
),
GoRoute(
path: '/dialog',
pageBuilder: (context, state) => DialogPage(
key: state.pageKey,
child: const DialogScreen(),
),
),
],
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
class Page0Screen extends StatelessWidget {
const Page0Screen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 0')),
body: Center(
child: ElevatedButton(
onPressed: () {
GoRouter.of(context)
..push('/page-1')
..push('/dialog');
},
child: const Text('Push'),
),
),
);
}
}
class Page1Screen extends StatelessWidget {
const Page1Screen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: const Center(
child: Text('Page 1'),
),
);
}
}
class DialogScreen extends StatelessWidget {
const DialogScreen({super.key});
#override
Widget build(BuildContext context) {
return const AlertDialog(
title: Text('Dialog'),
);
}
}
class DialogPage extends Page {
const DialogPage({
required this.child,
super.key,
});
final Widget child;
#override
Route createRoute(BuildContext context) {
return DialogRoute(
settings: this,
context: context,
builder: (context) {
return child;
},
);
}
}
Assuming your goal is to display a dialog you can use the showDialog function in flutter.
Below is a sample
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Basic dialog title'),
content: const Text('A dialog is a type of modal window that\n'
'appears in front of app content to\n'
'provide critical information, or prompt\n'
'for a decision to be made.'),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Disable'),
onPressed: () {
GoRouter.of(context).pop();
},
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Enable'),
onPressed: () {
GoRouter.of(context).pop();
},
),
],
);
},
);
go_router doesn't support pushing two routes at the same time. And it is not a good practice to push 2 pages at the same time.
What can you do instead?
You can transition from page1 to page2
Go to dialog page in the init method of the page2 using context.go('/dialog');
On exiting dialog page you can use context.pop() which will land you in page1

flutter int got to zero

I have a screen with a one button and anotherone with a Container to show a number. I declared a variable in the StatlessWidget class. The button adds 1 to the variable , however after leaving the Class with the container und return to it, I noticed the widgets get updated and my variable loses its value. I have tried initializing it in initState() but it still loses it's value.
import 'package:flutter/material.dart';
import 'package:generator/route_generator.dart';
import 'package:generator/main.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/menu',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class Menu extends StatelessWidget {
int data = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Menu'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/second', arguments: data);
},
child: Text('go to the second'),
),
],
),
));
}
}
class FirstPage extends StatelessWidget {
int data = 0;
void eins() {
data = data + 25;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
data.toString(),
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/second', arguments: data);
},
child: Text('go to the second'),
),
RaisedButton(
child: Text('25'),
onPressed: eins,
)
],
),
));
}
}
class SecondPage extends StatelessWidget {
int data = 0;
SecondPage({Key key, #required this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
data.toString(),
style: TextStyle(fontSize: 20),
),
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/first');
},
child: Text('go to the first'),
),
],
),
));
}
}
another class
import 'package:flutter/material.dart';
import 'package:generator/main.dart';
import './main.dart';
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case '/first':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/third':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/menu':
return MaterialPageRoute(
builder: (_) => Menu(),
);
case '/second':
// if (args is int) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
//}
// return _errorRoute();
//default:
//return _errorRoute();
}
}
static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('ERROR'),
),
);
});
}
}
The first thing that is weird about your program is that you want to preserve state, in your case a counter variable, but to do that, you select a StatelessWidget. At the very least you will need a StatefulWidget. It's in the name already.
That said, it's not that easy, you may want to look up the different approaches to state management in Flutter: https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
To expand on what #nvoigt said, pick a state management solution instead of passing around arguments from page to page. This way you can keep your widgets stateless, which is preferred but not possible to do what you want to do without a state management solution.
Here's a quick way using GetX state management. This can be done using Provider, RiverPod, Bloc/Cubit...pick your poison.
Here's a new controller class with your data and logic.
class DataController extends GetxController {
int data = 0;
void eins() {
data += 25;
update();
}
}
Then a couple small changes to the rest of your good and you're good to go.
void main() {
Get.put(DataController()); // initializing your controller
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/menu',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class Menu extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Menu'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/second');
},
child: Text('go to the second'),
),
],
),
));
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<DataController>(); // finding controller
return Scaffold(
appBar: AppBar(
title: Text('First Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<DataController>( // wrap your text in GetBuilder to display variabe
builder: (_) {
return Text(
controller.data.toString(), // accessing variable via controller
);
},
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/second');
},
child: Text('go to the second'),
),
RaisedButton(
child: Text('25'),
onPressed: () {
controller.eins(); // accessing function via controller
}),
],
),
));
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.find<DataController>(); // finding same instance of controller on new page
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
GetBuilder<DataController>(
builder: (_) {
return Text(
controller.data.toString(),
);
},
),
RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed('/first');
},
child: Text('go to the first'),
),
],
),
));
}
}
// no longer need to pass anything in your router below
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/first':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/third':
return MaterialPageRoute(
builder: (_) => FirstPage(),
);
case '/menu':
return MaterialPageRoute(
builder: (_) => Menu(),
);
case '/second':
// if (args is int) {
return MaterialPageRoute(
builder: (_) => SecondPage(),
);
//}
// return _errorRoute();
//default:
//return _errorRoute();
}
}

Why Flutter Navigator 2.0 API not work with flutter_bloc?

I am trying to make navigation by serving pages via BLoC (flutter_bloc 6.1.1).
main.dart:
class MyApp extends StatelessWidget {
List<MaterialPage> _pages = [
MaterialPage( key: ValueKey('Page1'), child: Page1() ),
];
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BlocProvider(
create: (BuildContext context) => NavCubit(),
// WHY PAGES NOT UPDATES HERE FROM BLoC?????
child: BlocListener<NavCubit, NavState>(
listener: (context, state) {
_pages = state.pages;
},
child: Navigator(
pages: _pages,
onPopPage: (route, result) {
if ( !route.didPop(result) ) {
return false;
}
return true;
},
)
)
),
);
}
}
The first page (Page 1) has a button, clicking on which should navigate to the second page (Page2)
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => NavCubit(),
child: Scaffold(
appBar: AppBar( title: Text('Page 1') ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("page 11111111111"),
RaisedButton(
child: Text('Go to: Page 2', style: TextStyle(fontSize: 20)),
onPressed: () {
print("goto page2 btn");
context.read<NavCubit>().navigateTo();
},
),
],
)
),
),
);
}
}
class NavCubit extends Cubit<NavState> {
...
void navigateTo() {
final navState = NavState([MaterialPage( key: ValueKey('Page2'), child: Page2() )]);
debugPrint(navState.toString());
emit(navState);
}
}
But it doesn't happen!
As far as I can see, the BLoC State are updated good. But no screen changes...
SEE PLS:
Here my full test project: https://github.com/morfair/flutter_test_app/tree/master/lib
In Page1 you are creating another NavCubit in the BlocProvider. Therefore you have 2 NavCubits in total. So when you then call context.read<NavCubit>.navigateTo(), you are calling this method on the wrong NavCubit. Try to remove the creation of a second cubit in the BlocProvider in the Page1 build method.

Error: Could not find the correct Provider<Cart> above this Consumer<Cart> Widget

I put provider above material app so I can use it in every widget in-app right?
so why this error
and my code is
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Cart(),
),
ChangeNotifierProvider.value(value: ProductsProvider()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.deepOrange,
fontFamily: 'Lato',
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ProductOverviewScreen(),
routes: {ProductDetailScreen.routeName: (ctx) => ProductDetailScreen()},
),
);
}
}
and this screen has the error
enum filterOptions { Favorites, All }
class ProductOverviewScreen extends StatefulWidget {
#override
_ProductOverviewScreenState createState() => _ProductOverviewScreenState();
}
class _ProductOverviewScreenState extends State<ProductOverviewScreen> {
var _showOnlyFavorites = false;
#override
Widget build(BuildContext context) {
//final cart = Provider.of<Cart>(context);
return Scaffold(
appBar: AppBar(
title: Text("MyShop"),
actions: [
PopupMenuButton(
onSelected: (selectedValue) {
setState(() {
if (selectedValue == filterOptions.Favorites) {
_showOnlyFavorites = true;
} else if (selectedValue == filterOptions.All) {
_showOnlyFavorites = false;
}
});
},
icon: Icon(Icons.more_vert),
itemBuilder: (_) => [
PopupMenuItem(
child: Text("Only Favorites"),
value: filterOptions.Favorites),
PopupMenuItem(
child: Text("Show All"), value: filterOptions.All),
]),
Consumer<Cart>(
builder: (_, cartData, ch) => Badge(
child: ch,
value: cartData.itemCount.toString(),
),
child: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {},
),
)
],
),
body: ProductsGrid(_showOnlyFavorites));
}
}
the error in the consumer is
Error: Could not find the correct Provider above this Consumer Widget
why does this screen cant know the Cart provider?
any help please ?
I don't have enough reputation to comment, but your sample works fine on my end with flutter 1.22.5 and provider 4.3.2. However, I managed to reproduce your problem when accidentally importing a package named flutter_provider and using its Consumer widget. Couldn't imagine this being your problem though.
By the way, you should avoid using the value constructor to create your ChangeNotifier. Either pass a variable or use the default constructor with the create parameter.