Flutter ViewModel old data is repopulating (using Provider) - flutter

I'm using multiProvider to manage state in flutter. The main problem that i'm facing is;
Unable to clear the data inside my viewModel after closing a page. When i open the page again, old data is populating from viewModel.
Because of this issue i've created a 'reset' method in my viewModel & calling this 'reset' method before open the page.
Is there any way to remove old values from viewModel :- Please suggest
My code:
Main Page
Adding providers under multi provider
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: ViewModel1()),
ChangeNotifierProvider.value(value: ViewModel2()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
),
//Set Home Page after splash
home: HomePage(),
)
);
}
}
This is my Homepage Navigate to FirstPage & reset ViewModel1 data
HomePage()
{
:
:
:
onPressed(){
Navigator.push(
context, MaterialPageRoute(builder: (context) => FirstPage()));
Provider.of<ViewModel1>(context, listen: false).reset();
}
}
This is My Page:
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
final ViewModel1 _viewModel1 =
Provider.of<ViewModel1>(context, listen: true);
TextFormField(
controller: _fNameController,
onChanged: _viewModel1.setFname,
);
}
}
This is my ViewModel:
class ViewModel1 with ChangeNotifier {
String _fName = '';
String get fName => _fName;
setFname(String fName) {
_fName = fName;
notifyListeners();
setFnameValidation(fName
.trim()
.isNotEmpty ? true : false);
}
//TODO Reset all values
void reset() {
_fName = '';
notifyListeners();
}
}

If you are reusing one model on different pages, without sharing data between pages.. It's better to create and close the model on every page.

Related

Provider to be initialized asynchronously from `initState()` but get `could not find the correct Provider`

I develop an ad app, with a message button on the detailed view.
When the user tap on it, the chats view (stateful widget) is pushed to the screen.
The initState() is there to call the asyncInitMessages() which asynchronously fetches the chats and related message from the distant database. The asyncInitMessages() belongs to the Chats class which extends ChangeNotifier.
/// A chat conversation
class Chats extends ChangeNotifier {
/// Internal, private state of the chat.
void asyncInitMessages(
{required ClassifiedAd ad,
required String watchingUserId,
required bool isOwner}) async {
// blah blah
}
}
The ClassifiedAdMessagesViewstateful widget class implementation is as follows (snipet):
#override
void initState() {
// == Fetch conversation and messages
asyncInitMessages();
}
void asyncInitMessages() async {
// === Update all messages
try {
Provider.of<Chats>(context, listen: false).asyncInitMessages(
ad: widget.ad,
watchingUserId: widget.watchingUser!.uid,
isOwner: _isOwner);
} catch (e) {
if (mounted) {
setState(() {
_error = "$e";
_ready = true;
});
}
}
}
#override
Widget build(BuildContext context) {
// <<<<<<<<<<< The exception fires at the Consumer line right below
return Consumer<Chats>(builder: (context, chats, child) {
return Scaffold(
// ... blah blah
Finally, when running ll that, I got the exception in the build at the Consumer line:
could not find the correct Provider<chats>
Help greatly appreciated.
[UPDATED]
Here is the main (very far up from the messages screen)
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
//if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// } else {
// Firebase.app(); // if already initialized, use that one
// }
if (USE_DATABASE_EMULATOR) {
FirebaseDatabase.instance.useDatabaseEmulator(emulatorHost, emulatorPort);
}
runApp(RootRestorationScope(
restorationId: 'root',
child: ChangeNotifierProvider(
create: (context) => StateModel(),
child: const App())));
}
class App extends StatefulWidget {
const App({super.key});
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return PersistedAppState(
storage: const JsonFileStorage(),
child: MultiProvider(
providers: [
ChangeNotifierProvider<ThemeModel>.value(value: _themeModel),
//ChangeNotifierProvider<AuthModel>.value(value: _auth),
],
child: Consumer<ThemeModel>(
builder: (context, themeModel, child) => MaterialApp(
// blah blah
}
}
}
And the component just on top of the
/// Classified ad detail view
class ClassifiedAdDetailView extends StatefulWidget {
final User? watchingUser;
final ClassifiedAd ad;
const ClassifiedAdDetailView(
{Key? key, required this.watchingUser, required this.ad})
: super(key: key);
#override
State<ClassifiedAdDetailView> createState() => _ClassifiedAdDetailViewState();
}
class _ClassifiedAdDetailViewState extends State<ClassifiedAdDetailView>
with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Chats(),
builder: ((context, child) => Scaffold(
// blah blah
ElevatedButton(
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ClassifiedAdMessagesView(
ad: ad,
watchingUser: widget.watchingUser)));
}),
Providers must be located in the widget tree above the widget where you want to use them with Consumer or Provider.of. When you push a new route with Navigator, it won't be add the pushed route below the widget from where you push, it will add it at the same level where home of MaterialApp is located.
(I think the error message you get also states that you can't access the providers between routes.)
In general the tree will look like this if you push some routes (check it with the Flutter Widget Inspector):
MaterialApp
home
widget1
widget2
widget21
widget22
page1
widget1
widget2
page2
page3
In your code you create the provider in ClassifiedAdDetailView and then push
ClassifiedAdMessagesView from this in the onPressed method. You won't be access this provider from ClassifiedAdMessagesView because the tree will be like (simplified):
MaterialApp
home
ClassifiedAdDetailView
ClassifiedAdMessagesView
The solution is to "lift the state up" and place the provider above every widget from where you need to access it. It can be a part of your existing Multiprovider above MaterialApp but if it is too far, you need to find a proper place that is above both ClassifiedAdDetailView and ClassifiedAdMessagesView.

How to set ThemeMode in splash screen using value stored in sqflite FLUTTER

I have a Flutter Application where an sqflite database stored the user preference of ThemeMode (viz Dark, Light and System). I have created a splash screen using flutter_native_splash which supports dark mode too.
The Problem is this that I want the splash screen to follow the users stored value for theme mode. Currently, the code I am using is as follows:
class MyRoot extends StatefulWidget {
// const MyRoot({Key? key}) : super(key: key);
static ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);
#override
State<MyRoot> createState() => _MyRootState();
}
class _MyRootState extends State<MyRoot> {
DatabaseHelper? databaseHelper = DatabaseHelper.dhInstance;
ThemeMode? tmSaved;
#override
void initState() {
Future.delayed(Duration.zero, () async => await loadData());
super.initState();
}
#override
Widget build(BuildContext context) {
//to prevent auto rotation of the app
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return ValueListenableBuilder<ThemeMode>(
valueListenable: MyRoot.themeNotifier,
builder: (_, ThemeMode currentMode, __) {
return Sizer(
builder: (context, orientation, deviceType) {
return MaterialApp(
title: 'My Application',
theme: themeLight, //dart file for theme
darkTheme: themeDark, //dart file for theme
themeMode: tmSaved ?? currentMode,
initialRoute: // my initial root
routes: {
// my routes
.
.
.
// my routes
},
);
},
);
},
);
}
Future<void> loadData() async {
if (databaseHelper != null) {
ThemeMode? themeMode= await databaseHelper?.selectStoredTheme(); // function retrieving sqflite stored value and returning ThemeMode value
if (themeMode != null) {
MyRoot.themeNotifier.value = themeMode;
return;
}
}
MyRoot.themeNotifier.value = ThemeMode.system;
}
}
Currently, this shows a light theme splash screen loading, then converts it into dark with a visible flicker.
ValueListenableBuilder<ThemeMode>(... is to enable real time theme change from settings page in my app which working as intended (taken from A Goodman's article: "Flutter: 2 Ways to Make a Dark/Light Mode Toggle".
main.dart has the below code:
void main() {
runApp(MyRoot());
}
Have you tried loading the setting from sqflite in main() before runApp? If you can manage to do so, you should be able to pass the setting as argument to MyRoot and then the widgets would be loaded from the start with the correct theme. I'm speaking in theory, I can't test what I'm suggesting right now.
Something like:
void main() async {
ThemeMode? themeMode= await databaseHelper?.selectStoredTheme(); // function retrieving sqflite stored value and returning ThemeMode value
runApp(MyRoot(themeMode));
}
[...]
class MyRoot extends StatefulWidget {
ThemeMode? themeMode;
const MyRoot(this.themeMode, {Key? key}) : super(key: key);
static ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);
#override
State<MyRoot> createState() => _MyRootState();
}
EDIT
Regarding the nullable value you mentioned in comments, you can change the main like this:
void main() async {
ThemeMode? themeMode= await databaseHelper?.selectStoredTheme(); // function retrieving sqflite stored value and returning ThemeMode value
themeMode ??= ThemeMode.system;
runApp(MyRoot(themeMode!));
}
which makes themeMode non-nullable, and so you can change MyRoot in this way:
class MyRoot extends StatefulWidget {
ThemeMode themeMode;
const MyRoot(required this.themeMode, {Key? key}) : super(key: key);
[...]
}
Regarding the functionality of ValueNotifier, I simply thought of widget.themeMode as the initial value of your tmSaved property in your state, not as a value to be reused in the state logic. Something like this:
class _MyRootState extends State<MyRoot> {
DatabaseHelper? databaseHelper = DatabaseHelper.dhInstance;
late ThemeMode tmSaved;
#override
void initState() {
tmSaved = widget.themeMode;
super.initState();
}
[...]
}
so that your widgets would already have the saved value at the first build.
PS the code in this edit, as well as in the original part, isn't meant to be working by simply pasting it. Some things might need adjustments, like adding final to themeMode in MyRoot.
Make your splashscreen. A main widget which get data from sqlflite
And make splashscreen widget go to the your home widget with remove it using navigation pop-up
for example :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'ToDo',
color: // color of background
theme: // theme light ,
darkTheme: // darktheme
themeMode: // choose default theme light - dark - system
home: Splashscreen(),// here create an your own widget of splash screen contains futurebuilder to fecth data and return the mainWidget ( home screen for example)
);
}
}
class Splashscreen extends StatelessWidget {
Future<bool> getData()async{
// get info
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (context,snapshot){
// if you want test snapshot
//like this
if(snapshot.hasData) {
return Home();
} else {
return Container(color: /* background color as same as theme's color */);
}
}
);
}
}

How to make Flutter with GetX wait until data is loaded

I'm looking for a best-practice way to implement waiting for my app to initialize data before displaying the first page.
The app has a main controller as well as a controller per page. The main controller initially loads data from a server, and until that's done I'd like to display a splash page (or at least wait before opening the actual app-page)
An simple solution would be, that a page waits for the main controller to be initialized
class MainController extends GetxController {
final isInitialized = false.obs;
#override
void onInit() async {
Future f1 = server.get('service1').then(....)
Future f2 = server.get('service1').then(....)
Future f3 = server.get('service1').then(....)
await Future.wait([f1, f2, f3]);
isInitialized.value = true;
}
}
And a page component could:
class HandleTaskPage extends GetView<HandleTaskPageController> {
#override
Widget build(BuildContext context) {
MainController mainController = Get.find();
return Obx(() {
if (mainController.isInitialized().value) {
return TaskPanelWidget();
} else {
return WaitingPage();
}
})
}
}
But my app allows the user to start at any given page using a direct url (web-app) e.g. http://app.com/showtask/123
Which means that I must put the wait for global controller on every page.
Is there some way I could simply make Get wait (and possibly display a Welcome page) until GlobalController is ready before moving on to the page described in the route?
I've tried to add a WelcomePage to GetMaterialApp, to stop the app from going directly to the requested url. The WelcomeController should then await MainController before redirecting. But even though welcome-page does get rendered, the app still automatically continues to the page requested in the url.
void main() {
runApp(GetMaterialApp(
home: WelcomePage(),
...
...
...
You can implement mixin with name StateMixin.
Example (Controller):
class UserController extends GetxController with StateMixin {
getData() {
// make status to loading
change(null, status: RxStatus.loading());
// Code to get data
await service.getData()
// if done, change status to success
change(null, status: RxStatus.success());
}
}
Example (UI):
class HomePage extends GetView<UserController> {
#override
Widget build(BuildContext context) {
// controller from GetView
return controller.obx((state) {
return OtherWidget()
},
onLoading: CircularProgressIndicator(),
)
}
}
You can use the builder method on MaterialApp to build/show the widget you want.
Using GetX - you can show a loader while waiting for your data to load as follows:
Widget build(BuildContext context) {
final MainController mainController = Get.find();
final isInitialized = mainController.isInitialized().value;
return GetMaterialApp(
title: 'Example',
debugShowCheckedModeBanner: false,
theme: ...,
initialRoute: '/',
getPages: [...],
builder: (context, child) => isInitialized
? Container(child: child)
: const Center(
child: CircularProgressIndicator(color: Colors.blue),
),
);
}
The above should display a blue loader until isInitialized becomes true.

Flutter - Using GetIt with BuildContext

I'm using Localizations in my app based on the flutter documentation.
See here: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
I use get_it package (version 4.0.4) to retrieve singleton objects like the Localization delegate. Unfortunately it needs a BuildContext property. Sometimes in my app I don't have the context reference so it would be nice if it would work like this: GetIt.I<AppLocalizations>() instead of this: AppLocalizations.of(context). It still can be achieved without a problem if you setup get_it like this: GetIt.I.registerLazySingleton(() => AppLocalizations.of(context)); The problem is that you need the context at least once to make it work. Moreover if you would like to display a localized text instantly in your initial route it's more difficult to get a properly initialized BuildContext at a time when you need it.
It's a little hard for me to explain it properly so I recreated the issue in a minimal example.
I commented out some code that would cause compile time errors, but it shows how I imagined it to be done.
main.dart
GetIt getIt = GetIt.instance;
void setupGetIt() {
// How to get BuildContext properly if no context is available yet?
// Compile time error.
// getIt.registerLazySingleton(() => AppLocalizations.of(context));
}
void main() {
setupGetIt();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
// The above line also won't work. It has BuildContext but Applocalizations.of(context) won't work
// because it's above in the Widget tree and not yet setted up.
getIt.registerLazySingleton(() => AppLocalizations.of(context));
return MaterialApp(
supportedLocales: const [
Locale('en', 'US'),
Locale('hu', 'HU'),
],
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback: (locale, supportedLocales) {
// check if locale is supported
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale?.languageCode &&
supportedLocale.countryCode == locale?.countryCode) {
return supportedLocale;
}
}
// if locale is not supported then return the first (default) one
return supportedLocales.first;
},
// You may pass the BuildContext here for Page1 in it's constructor
// but in a more advanced routing case it's not a maintanable solution.
home: Page1(),
);
}
}
Initial route
class PageBase extends StatelessWidget {
final String title;
final Widget content;
PageBase(this.title, this.content);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: content,
);
}
}
class Page1 extends PageBase {
// It won't run because I need the context but clearly I don't have it.
// And in a real app you also don't want to pass the context all over the place
if you have many routes to manage.
Page1(String title)
: super(AppLocalizations.of(context).title, Center(child: Text('Hello')));
// Intended solution
// I don't know how to properly initialize getIt AppLocalizations singleton by the time
// it tries to retrieve it
Page1.withGetIt(String title)
: super(getIt<AppLocalizations>().title, Center(child: Text('Hello')));
}
locales.dart
String globalLocaleName;
class AppLocalizations {
//AppLocalizations(this.localeName);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
static Future<AppLocalizations> load(Locale locale) async {
final String name =
locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
globalLocaleName = localeName;
return AppLocalizations();
});
}
String get title => Intl.message(
'This is the title.',
name: 'title',
);
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
// This delegate instance will never change (it doesn't even have fields!)
// It can provide a constant constructor.
const _AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) {
return ['en', 'hu'].contains(locale.languageCode);
}
#override
Future<AppLocalizations> load(Locale locale) => AppLocalizations.load(locale);
#override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
And some intl generated dart code and .arb files that is not so important to illustrate the problem.
So all in all, how can I achive to use my AppLocalizations class as a singleton without using a context for example in a situation like this? Maybe my initial approach is bad and it can be done in other ways that I represented. Please let me know if you have a solution.
Thank you.
To achieve what you have described you need to first make the navigation service using get_it. Follow these steps to achieve the result :
1. Create a navigation service
import 'package:flutter/material.dart';
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!
.push(routeName);
}
goBack() {
return navigatorKey.currentState!.pop();
}
}
This allows you to navigate anywhere from any point throughout the app without build context. This navigator key is what you can use to achieve the AppLocalization instance for the current context.
Refer to the FilledStacks tutorials for this method of navigating without build context.
https://www.filledstacks.com/post/navigate-without-build-context-in-flutter-using-a-navigation-service/
2. Register
GetIt locator = GetIt.instance;
void setupLocator() {
...
locator.registerLazySingleton(() => NavigationService());
...
}
3. Assign the navigator key in the material app
return MaterialApp(
...
navigatorKey: navigationService.navigatorKey,
...
),
3. Create an instance for the AppLocalizations and import it wherever you want to use
localeInstance() => AppLocalizations.of(locator<NavigationService>().navigatorKey.currentContext!)!;
3. The actual use case
import 'package:{your_app_name}/{location_to_this_instace}/{file_name}.dart';
localeInstance().your_localization_variable
You can add a builder to your MaterialApp and setup the service locator inside it with the context available. Example:
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, widget) {
setUpServiceLocator(context);
return FutureBuilder(
future: getIt.allReady(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return widget;
} else {
return Container(color: Colors.white);
}
});
},
);
}
Service Locator Setup:
void setUpServiceLocator(BuildContext context) {
getIt.registerSingleton<AppLocalizations>(AppLocalizations.of(context));
}
You could use some non-localizable splash screen with FutureBuilder and getIt.allReady().
Something like:
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: getIt.allReady(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// Navigate to main page (with replace)
} else if (snapshot.hasError) {
// Error handling
} else {
// Some pretty loading indicator
}
},
);
}
I'd like to recommend the injectable package for dealing with get_it also.

How to pass parameters to flutter web app

After hours of searching about the topic and due to lack of documentation on Flutter Web I am asking this question.
I was trying to create a web app using flutter and had an requirement where URL such as below
website.com/user/someUserCode
would be called and an page will be launched where the data (someUserCode) will be passed to the page
but haven't got any solutions yet to resolve it.
so just rounding it all up,
How to pass and fetch the data using (get / post) methods to flutter web app?
EDIT 1
What all I know / have tried yet
I am using below code to read if some parameters are being to some class file
final Map<String, String> params = Uri.parse(html.window.location.href).queryParameters;
String data = params["userData"];
all this actually solves the Fetch part of my question (maybe)
but the part where that data will be passed to the page via URL is still missing.
EDIT 2
Since I haven't got any replies and was not able to find anything i raised an ticket on Flutter GitHub page here
anyone else looking for the same issue can track it there (if it gets resolve)
May you could do it in a easy way:
import 'dart:html';
import 'package:flutter/material.dart';
import 'home_page.dart';
void getParams() {
var uri = Uri.dataFromString(window.location.href);
Map<String, String> params = uri.queryParameters;
var origin = params['origin'];
var destiny = params['destiny'];
print(origin);
print(destiny);
}
void main() {
getParams();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Your app',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
And then call it from browser:
http://localhost:52695/?origin=pointA&destiny=pointB
Output:
pointA
pointB
I tried the above method from #Mariano Zorrilla but it still opened the pages in order:
/
/user
/user/yFbOfUAwx1OCC93INK8O7VqgBXq2
I have found Fluro, and works efficiently and cleanly you only need to add one routing file and do all the routing in one file rather than editing every page you want to route to, here's how you would implement it:
main.dart
void main() {
FluroRouter.setupRouter();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Website Title',
onGenerateRoute: FluroRouter.router.generator
);
}
}
fluro_router.dart
class FluroRouter {
static Router router = Router();
//Define your routers here
static void setupRouter() {
router.define('/', handler: _homeHandler);
router.define('/login', handler: _loginHandler);
router.define('/online-enquiry/:userId', handler: _userHandler);
}
//Add your handlers here
static Handler _homeHandler = Handler(handlerFunc: (context, Map<String, dynamic> params) => Home());
static Handler _loginHandler = Handler(handlerFunc: (context, Map<String, dynamic> params) => Login());
static Handler _userHandler = Handler(handlerFunc: (context, Map<String, dynamic> params) => UserProfile(userID: params['userId'].first));
}
Source
You can get everything (paths, parameters, etc) from onGenerateRoute. Your Home will be / and everything from there can be grabbed and used to redirect users.
My approach to solve this is the following. Your base App() should be like:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Website Title',
onGenerateRoute: (settings) => NavigatorRoute.route(settings.name),
);
}
}
and the class NavigatorRoute will be:
class NavigatorRoute extends StatefulWidget {
final String path;
static Route<dynamic> route(String path) {
return SimpleRoute(
name: '', // this one is always empty as you didn't route yet
title: 'Website Title',
builder: (_) => NavigatorRoute(path: path),
animated: false
);
}
const NavigatorRoute({Key key, this.path}) : super(key: key);
#override
_NavigatorRouteState createState() => _NavigatorRouteState();
}
class _NavigatorRouteState extends State<NavigatorRoute> {
#override
void initState() {
super.initState();
Future.microtask(() {
if (widget.path == '/') {
Navigator.of(context).pushAndRemoveUntil(HomeScreen.route(false), (_) => false);
return;
} else if (widget.path == '/user') {
Navigator.of(context).pushAndRemoveUntil(UserScreen.route(false), (_) => false);
return;
} else if (widget.path.contains('/user/')) {
Navigator.of(context).pushAndRemoveUntil(UserScreen.routeCode(widget.path.split('/')[2]), (_) => false);
return;
} else if (widget.path == '/about') {
Navigator.of(context).pushAndRemoveUntil(AboutScreen.route(), (_) => false);
return;
} else {
Navigator.of(context).pushAndRemoveUntil(HomeScreen.route(), (_) => false);
return;
}
});
}
#override
Widget build(BuildContext context) {
return SizedBox();
}
}
The code for the SimpleRoute is:
class SimpleRoute extends PageRoute {
SimpleRoute({#required String name, #required this.title, #required this.builder, #required this.animated})
: super(settings: RouteSettings(name: name));
final String title;
final WidgetBuilder builder;
final bool animated;
#override
Color get barrierColor => null;
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 200);
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return animated
? FadeTransition(
opacity: animation,
child: Title(
title: this.title,
color: Theme.of(context).primaryColor,
child: builder(context),
),
)
: Title(
title: this.title,
color: Theme.of(context).primaryColor,
child: builder(context),
);
}
}
So, finally... if you want to easily open one of your screens, you can do:
class HomeScreen extends StatefulWidget {
static Route<dynamic> route(bool animated) {
return SimpleRoute(name: '/', title: 'Home', builder: (_) => HomeScreen(), animated: animated);
}
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
...
}
The routeCode could be:
static Route<dynamic> routeCode(String id) {
return SimpleRoute(name: '/user/$id', title: 'User', builder: (_) => UserScreen(id: id), animated: false);
}
The main benefit of doing this is avoiding the stack of pages generated by accessing the last screen.
For example, if you're using directly the onGenerateRoute for "www.mywebsite.com/user/userId/edit" then Flutter will open:
Home Screen
User Screen
UserId Screen
Edit Screen
but with this approach, only "Edit Screen" will be open.