In Flutter we can customize the view-change animation by extending PageRoute (or by using a class which extends that).
For instance, I'm changing the animation to "slide" in my MaterialApplication by using the CupertinoPageRoute that way:
Navigator.of(context).pushReplacement(
CupertinoPageRoute(builder: (context) => Calendar()),
);
Now I want to change that by using named views defined in the main.dart file:
return MaterialApp(
title: 'Demo',
theme: myTheme, // => Theme.of(context).copyWith(...)
initialRoute: '/',
routes: {
'/': (context) => Login(),
'/calendar': (context) => Calendar(),
}
);
This way I can just call
Navigator.of(context).pushReplacementNamed('/calendar');
Which is IMO clearer and view-agnostic.
The issue with this approach is that I can't define a PageRoute, so I can't customize the view-change animation.
Is there a way to do that?
I took chemamolins' advice and solved it in a similar way, but using maps.
I "extracted" the routes object and put it outside MaterialApp:
var routes = {
'/': (context) => Login(),
'/calendar': (context) => Calendar()
};
Then I used it inside onGenerateRoute:
Widget build(BuildContext context) {
var routes = {
'/': (context) => Login(),
'/calendar': (context) => Calendar()
};
return MaterialApp(
title: 'Demo',
theme: myTheme,
initialRoute: '/',
onGenerateRoute: (settings) {
return CupertinoPageRoute(builder: (context) => routes[settings.name](context));
}
);
}
You could leverage onGenerateRoute()
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
Route onGenerateRoute(RouteSettings settings) {
Route page;
switch (settings.name) {
case "/":
page = CupertinoPageRoute(builder: (context) => Login());
break;
case "/calendar":
page = CupertinoPageRoute(builder: (context) => Calendar());
break;
}
return page;
}
#override
Widget build(BuildContext context) {
return new WidgetsApp(
onGenerateRoute: onGenerateRoute,
initialRoute: "/",
);
}
}
Let's give a look to the Flutter code itself.
There is a framework provided onGenerateRoute() method called to generate the routes.
Look at the following snippet taken from the app.dart file in the framework.
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
final String name = settings.name;
WidgetBuilder builder;
if (name == Navigator.defaultRouteName && widget.home != null) {
builder = (BuildContext context) => widget.home;
} else {
builder = widget.routes[name];
}
if (builder != null) {
return new MaterialPageRoute<dynamic>(
builder: builder,
settings: settings,
);
}
if (widget.onGenerateRoute != null)
return widget.onGenerateRoute(settings);
return null;
}
If the routes: provides a builder for a given name, it is used to generate the route using MaterialPageRoute by default. If it is not provided, it goes to generate it by using your onGenerateRoute() method.
Related
Please help.
I'm trying to display the photo captured in 'Generated1Group1Widget1.dart' to 'GeneratedResultsWidget.dart'. However, 'main.dart' is having some errors.
GeneratedGroup1Widget1.dart
class GeneratedGroup1Widget1 extends StatefulWidget {
#override
_GeneratedGroup1Widget1State createState() => _GeneratedGroup1Widget1State();
}
class _GeneratedGroup1Widget1State extends State<GeneratedGroup1Widget1> {
XFile? _image;
Future _pickImage() async {
final imageSource = await showDialog<ImageSource>(
context: context,
builder: (context) => SimpleDialog(
title: const Text('Select Image Source'),
children: [
SimpleDialogOption(
onPressed: () => Navigator.pop(context, ImageSource.camera),
child: const Text('Camera'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(context, ImageSource.gallery),
child: const Text('Gallery'),
),
],
),
);
if (imageSource != null) {
final image = await ImagePicker().pickImage(source: imageSource);
setState(() {
_image = image;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GeneratedResultsWidget(image: _image),
),
);
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => _pickImage(),
...
GeneratedResultsWidget.dart
class GeneratedResultsWidget extends StatelessWidget {
final XFile? image;
GeneratedResultsWidget({
required this.image,
});
#override
Widget build(BuildContext context) {
return Material(
child: ClipRRect(
...
main.dart
void main() {
runApp(food_classifierApp());
}
class food_classifierApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: Size(360, 640),
builder: (BuildContext context,child) => MaterialApp(
title: 'food-classifier',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/GeneratedHomepageWidget',
routes: {
'/GeneratedScanWidget': (context) => GeneratedScanWidget(),
'/GeneratedResultsWidget': (context) => GeneratedResultsWidget(image: _image),
'/GeneratedHomepageWidget': (context) => GeneratedHomepageWidget(),
'/GeneratedFoodlistWidget': (context) => GeneratedFoodlistWidget(),
},
),
);
}
}
Error
Undefined name '_image'.
Try correcting the name to one that is defined, or defining the name.
I already searched up google but I can't find answers to my question. Thanks in advance!
Where exactly you define _image variable in your food_classifierApp class?
You should use state management to access your image or ... in every where of your app
You need to create model to store your picked image file from GeneratedGroup1Widget1.
class AppModel {
String fileName;
AppModel(this.fileName);
}
Now you can call it from GeneratedGroup1Widget1
Navigator.pushNamed(context, '/GeneratedResultsWidget',
arguments: AppModel('your file or file name'));
And recieve it here
class GeneratedResultsWidget extends StatelessWidget {
const GeneratedResultsWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as AppModel;
return Scaffold(body: Center(child: Text(args.fileName)));
}
}
Route should be
routes: {
//***
'/GeneratedResultsWidget': (context) => GeneratedResultsWidget(),
//***
}
i have a problem with my ChangeNotifierProvider widget. my proivder cant be found so i cant update my target widget. there is similar errors that was solved but i cant understand the fixes as i am new on ChangeNotifier. So if you interested in help me, please tell clearly.
Here is my error:
Error: Could not find the correct Provider above this GamePage Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that GamePage is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>().toString()),
);
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context, child) {
// No longer throws
return Text(context.watch<Example>().toString());
}
);
}
and here is my Codes:
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
Adverter.initialization();
runApp(MyInheritor(child: YirmiDortteDokuz()));
class YirmiDortteDokuz extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: '9in24 Game',
theme: ThemeData(
),
home: ChangeNotifierProvider( create: (BuildContext context) => Adverter(),
child: MeetScreenPage()),
);
}
}
Here is the route to GamePage.dart from MeetScreenPage.dart. there is no ChangeNotifierProvider stuff here and it extends StatefulWidget:
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>
GamePage(step: 0, pushes: [0], isRetry: false)));
Here is consumer builder and consumer widget in GamePage.dart class. it extends StatefulWdiget too.
class GamePageState extends State<GamePage>{
#override
Widget build(BuildContext context) {
Adverter _adverter = Provider.of<Adverter>(context, listen: false);
...
Consumer<Adverter>(builder: (context, data, child) {
return Text("Rewarded Point is ${_adverter.getrewardpoint()}");
}),
}
And my Adverter.dart Class extends ChangeNotifier:
class Adverter extends ChangeNotifier{
int _rewardedPoint = 0 ;
int getrewardpoint() => _rewardedPoint;
...
void showRewardedAd_second() {
if (_rewardedAd == null) {
return;
}
_rewardedAd.show(
onUserEarnedReward: (RewardedAd ad, RewardItem rewardItem) {
print("${rewardItem.amount} SANÄ°YE KAZANILDI.");
_rewardedPoint = _rewardedPoint + rewardItem.amount;
notifyListeners();
}
);
...
}
}
Please help and tell clearly to teach me. Thank you for your helps.
You have to set the type of ChangeNotifierProvider to Adverter like:
home: ChangeNotifierProvider<Adverter>(
create: (BuildContext context) => Adverter(),
child: MeetScreenPage()),
If you still can't access the provider, you can try to move it above MaterialApp, for example:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
Adverter.initialization();
runApp(MyInheritor(child: YirmiDortteDokuz()));
class YirmiDortteDokuz extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Adverter>(
create: (BuildContext context) => Adverter(),
builder: (context, child) => MaterialApp(
title: '9in24 Game',
theme: ThemeData(),
home: MeetScreenPage()),
);
}
}
Also, when defining your provider, use with instead of extends:
class Adverter with ChangeNotifier...
i want wait 5 seconds on main page and display loading animation then navigate to another page.
here is my code
import 'mainPage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isLoading = true;
#override
void initState() {
super.initState();
loadData();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.cyan,
body: Builder(
builder: (context) => Center(
child: Container(
child: SpinKitCubeGrid(color: Colors.white, size: 50.0),
),
),
),
),
);
}
Future loadData() async {
return new Timer(Duration(seconds: 5), () {
setState(() {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => MainPage()));
});
});
}
}
but i got this error:
Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
what should i do?
Wrap MyApp with MaterialApp which will provide the right context to Navigator
void main() {
runApp(MaterialApp(home: MyApp()));
}
Maybe this will help U
static Route route() {
return MaterialPageRoute<void>(builder: (_) => MyApp());
}
onPressed: () => Navigator.of(context).push<void>(MainPage.route()),
................
static Route route() {
return MaterialPageRoute<void>(builder: (_) => MainPage());
}
onPressed: () => Navigator.of(context).push<void>(MyApp.route()),
Can you try it like this? I didn't run the code but showing the basic idea. Just pass the context and call it from build function.
class _MyAppState extends State<MyApp> {
bool isLoading = true;
#override
Widget build(BuildContext context) {
loadData(context);
return MaterialApp(
...
);
}
Future loadData(context) async {
...
}
}
You need a context to navigate with the Navigator.
To navigate without using context you can use a package called GetX
Example:
Add "Get" before your MaterialApp, turning it into GetMaterialApp
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
Navigate to a new screen:
Get.to(NextScreen());
I have this problem. In my App I'm using the Provider package to manage the login State. In the MaterialApp I also want to manage some sort of the user configuration, in this case the Theme selection.
If I try to use two times Provider.of<LoginService>(context) I'm receiving this error:
Could not find the correct Provider<LoginService> above this MyApp Widget
This likely happens because you used a `BuildContext` that does not include the provider
of your choice.
How can I use in Provider more of one time the Provider.of... or even two different Providers in a Widget (to, for instance, separate my LoginService and my UserconfigService)?
Thank you!
Actual code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<LoginService>(
create: (context) => LoginService(),
child: MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
routes: {
'/': (BuildContext context) {
var state = Provider.of<LoginService>(context);
if (state.isLoggedIn()) {
return HomeScreen();
} else {
return LoginScreen();
}
},
MentorScreen.id: (BuildContext context) => MentorScreen(),
},
)
);
}
My objective:
child: MaterialApp(
title: 'MyApp',
debugShowCheckedModeBanner: false,
theme: state.isDarkThemeEnabled() == true ? ThemeData.dark() : ThemeData.light(),
...
You can use MultiProvider instead of ChangeNotifierProvider.
Read more in here.
This type of Error comes when you use the context just after creating the ChangeNotifierProvider class.
Similarly, if you use the context of the Scaffold to showDialog gives a similar error.
Here is the answer that explains why this happens
For this Wrap, your MaterialApp Widget inside Builder Class which will wait for the class to build first then call the Provider.of<T>(context) method.
Builder(
builder: (context) {
return MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
routes: {
'/': (BuildContext context) {
var state = Provider.of<LoginService>(context);
if (state.isLoggedIn()) {
return HomeScreen();
} else {
return LoginScreen();
}
},
MentorScreen.id: (BuildContext context) => MentorScreen(),
},
);
},
),
and for Two Providers in the same widget.
Use MultiProvider.
here's code from one of my app.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => locator<FAuthService>(),
builder: (context, _) {
return MultiProvider(
child: MaterialApp(
onGenerateRoute: Router.onGenerateRoute,
initialRoute: initialRoute,
navigatorKey: locator<NavigationService>().globalKey,
debugShowCheckedModeBanner: false,
title: 'Demo',
theme: ThemeData(
primaryColor: Colors.black,
),
),
providers: [
ChangeNotifierProvider<HomeVM>(
create: (_) => locator<HomeVM>(),
),
ChangeNotifierProvider<LoginVM>(
create: (context) => locator<LoginVM>(),
),
],
);
});
}
}
I'm having a problem with my app.
The situation is: i have a very simple login system on the app and i save the logged user using SharedPreferences.
But if the user leaves the app and then return it will open the login screen again, so i want to skip the login screen if the user is logged.
So in my main i put a function to check if there is login information, if yes it would redirect right to the app page or to the login page if not.
But when i try to call the app page it always calls the page setted on the Home part.
How can i solve this?
Is there any way to make it ignore the Home?
Is there a way to make the "if" part on the home? Would be the better solution but its not possible.
Also i know i'm not using the best way to make this control, but it works (despite of this problem i have now, sure) and if you have any tips on making it better i would appreciate.
Heres my code:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
void main() => runApp(new MyApp());
class _MyAppState extends State<MyApp> {
Future<void> verificaLogin() async {
print("running ok"); //just to test if the function runs
final prefs = await SharedPreferences.getInstance();
final key = 'usuario';
final value = prefs.getString(key);
print('saved tester $value');
String usu = value; /
if (usu.isEmpty) {
BuildContext context;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginScreen()), //sends to loginscreen if not logged
);
}
if (usu.isNotEmpty) {
BuildContext context;
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) => Pedidos())); //sends to main (not main.dart) app page
}
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => verificaLogin());
}
Widget build(BuildContext context) {
return BotToastInit(
child: MaterialApp(
navigatorObservers: [BotToastNavigatorObserver()],
title: "Test App",
theme: ThemeData(
primarySwatch: Colors.green,
),
debugShowCheckedModeBanner: false,
home: LoginScreen(), //i'm calling the loginscreen, it ignores the function on the top
),
);
}
}
Please make one SplashScreen like below it resolves your issue..
and call this as home: SplashScreen(),
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
String loginData = "";
SharedPreferences sharedPreferences;
void initState() {
super.initState();
readFromStorage();
}
void readFromStorage() async {
sharedPreferences = await SharedPreferences.getInstance();
final key = 'usuario';
loginData = sharedPreferences.getString(key);
if (loginData.isNotEmpty) {
Future.delayed(const Duration(milliseconds: 3000), () {
setState(() {
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) => Pedidos()));
});
});
} else {
Future.delayed(const Duration(milliseconds: 3000), () {
setState(() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginScreen()),);
});
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlutterLogo(
size: 100.0,
),
],
)),
);
}
}
Hi you can use a FutureBuilder at vefiricaLoigin, and then Material App at home use verificaLogin,
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
verificaLogin() {
return FutureBuilder<Widget>(
future: SharedPreferences.getInstance(),
builder: (BuildContext context, prefs) {
final key = 'usuario';
final value = prefs.getString(key);
String usu = value;
if (usu.isEmpty) {
return LoginScreen()l
} else {
return Pedidos();
}
);
}
Widget build(BuildContext context) {
return MaterialApp(
title: "Test App",
theme: ThemeData(
primarySwatch: Colors.green,
),
debugShowCheckedModeBanner: false,
home: verificaLogin()
);
}