This is my first post, so sorry for errors. I have a problem I can't make a button which open radom page in my apk. The project is connected with Coronavirus.
You can easily use RouteGenerator and list with needed random pages for this task.
main.dart:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Random pages',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: 'start_page',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
route_generator.dart:
class RouteGenerator {
static List<String> myRandomPages = [
'first_page',
'second_page'
];
static String getRandomNameOfRoute(){
return myRandomPages[Random().nextInt(myRandomPages.length)];
}
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case 'start_page':
return MaterialPageRoute(builder: (_) => StartPage());
case 'first_page':
return MaterialPageRoute(builder: (_) => FirstPage()); // FirstPage - is just a Widget with your content
case 'second_page':
return MaterialPageRoute(builder: (_) => SecondPage());// Also custom Widget
//... other random or not pages
}
}
}
start_page.dart:
class StartPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Start page'),
),
body: Center(
child: RaisedButton(
child: Text('Go to random page'),
onPressed: () => Navigator.of(context).pushNamed(RouteGenerator.getRandomNameOfRoute()),
),
)
);
}
}
Related
In this simple flutter GitHub repository implementation we have a simple screen which we want to pass arguments and getting them in destination screen, for example
form ScreenA() we want pass arguments to ScreenB()
defined routes:
routes: {
'/page/one': (context) => ScreenA(),
'/page/two': (context) => ScreenB(),
'/page/three': (context) => ScreenC(),
},
fist of all we have ScreenA():
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Navigation with params'),
),
body: Center(
child: ElevatedButton(
child: Text('Click me!'),
onPressed: () {
Navigator.pushNamed(
context,
'/page/two',
arguments: PageArguments(
id: 1,
title: "Example Title"
),
);
},
),
),
);
}
}
and destination page as ScreenB() is:
class ScreenB extends StatelessWidget {
#override
Widget build(BuildContext context) {
final arguments = ModalRoute.of(context)!.settings.arguments;
/* getting currect route name */
print(ModalRoute.of(context)!.settings.name);
/* getting NULL arguments */
print(arguments);
return Scaffold();
}
}
class PageArguments{
final int id;
final String title;
PageArguments({required this.id, required this.title});
}
It seems that Persistent Bottom Navigation Bar package does not pass correctly route information, it could be a bug. If you put a breakpoint in the build method of ScreenB, you will see that not only the argument is null, but route name is also wrong.
I tried in main.dart with onGenerateRoute and it worked for me this way:
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
PersistentBottomNavBarItem(
title: 'Home',
icon: Icon(Icons.home),
routeAndNavigatorSettings: RouteAndNavigatorSettings(
initialRoute: '/page/one',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
switch (settings.name) {
case '/page/one':
builder = (BuildContext context) => ScreenA();
break;
case '/page/two':
builder = (BuildContext context) => ScreenB();
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
})),
PersistentBottomNavBarItem(title: 'Help', icon: Icon(Icons.help)),
];
}
About casting the argument, I agree with the other answer.
Main and MyApp for reference
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ScreenA(),
routes: {
'/page/one': (context) => ScreenA(),
'/page/two': (context) => ScreenB(),
},
);
}
}
Update ClassB with below code no modification required in ClassA
class ScreenB extends StatelessWidget {
#override
Widget build(BuildContext context) {
final arguments =
ModalRoute.of(context)!.settings.arguments as PageArguments;
print(ModalRoute.of(context)!.settings.name);
print(arguments.id);
print(arguments.title);
print("------");
return Scaffold();
}
}
Key update:
Typecasting the argument.
final arguments =
ModalRoute.of(context)!.settings.arguments as PageArguments;
Console log:
I am trying to get into the provider topic, however calling a function only works if I put it into a consumer
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text("some text"),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
));
}
As in this example, my state is not updated. However, it already works with a consumer.
floatingActionButton: Consumer<ClickerProvider>(
builder: (context, value, child) {
return FloatingActionButton(
onPressed: Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
)
Is there an error in my code?
You can copy paste run two full code below
Reason : Can not find ClickerProvider
Solution 1: Move ClickerProvider to upper level such as MyApp
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: MaterialApp(
Solution 2: Use Builder
body: Center(child: Builder(builder: (BuildContext context) {
return Text(context.watch<ClickerProvider>().getCounter.toString());
})),
floatingActionButton: Builder(builder: (BuildContext context) {
return FloatingActionButton(
onPressed: () =>
Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
full code 1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ClickerProvider extends ChangeNotifier {
int _count = 0;
int get getCounter {
return _count;
}
void incrementCounter() {
_count += 1;
notifyListeners();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
));
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(context.watch<ClickerProvider>().getCounter.toString()),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
));
}
}
full code 2
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ClickerProvider extends ChangeNotifier {
int _count = 0;
int get getCounter {
return _count;
}
void incrementCounter() {
_count += 1;
notifyListeners();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ClickerProvider()),
],
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(child: Builder(builder: (BuildContext context) {
return Text(context.watch<ClickerProvider>().getCounter.toString());
})),
floatingActionButton: Builder(builder: (BuildContext context) {
return FloatingActionButton(
onPressed: () =>
Provider.of<ClickerProvider>(context, listen: false)
.incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
);
}),
));
}
}
As you can refer in the source code of Consumer here:
Obtains [Provider] from its ancestors and passes its value to [builder].
The [Consumer] widget doesn't do any fancy work. It just calls [Provider.of]
in a new widget, and delegates its build implementation to [builder].
Provider.of<X> depends on value of listen (true or false) to trigger new State.build() to widgets and State.didChangeDependencies() for StatefulWidget.
Consumer<X> always update UI, as it uses Provider.of<T>(context), where listen is true
In this case, since your listen is set as false, but you're putting it in the Consumer which make it true. That's why the UI will update with Consumer
Using Riverpod + StateNotifier but I think with other providers there is the same issue.
I have an authentication StateNotifier class and StateNotifierProvider and have wrapped the MaterialApp widget into a Riverpod Consumer to rebuild the complete app/widget tree when user is no longer authenticated.
As soon as I navigate with pushReplacementNamed to a third page and update the state of the authenticationStateNotifierProvider, I can see the build method of the consumer wrapping the App is triggered and the state is updated (print(state)) but the home page and widget tree is not rebuilt.
Sample app with 3 screen with the issue:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final state = watch(authenticationNotifier.state);
print(state);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/second')
return MaterialPageRoute(builder: (_) => SecondScreen());
else
return MaterialPageRoute(builder: (_) => HomeScreen());
},
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HomeScreen'),
),
body: Column(
children: [
MaterialButton(
child: Text('Logout'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
MaterialButton(
child: Text('Second'),
onPressed: () => Navigator.pushReplacementNamed(
context,
'/second',
),
),
],
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: MaterialButton(
child: Text('Logout'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('LoginScreen'),
),
body: MaterialButton(
child: Text('Login'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
);
}
}
// Controller.
final authenticationNotifier =
StateNotifierProvider((ref) => AuthenticationNotifier());
class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
AuthenticationNotifier() : super(Unauthenticated());
void toggle() {
state = state is Unauthenticated ? Authenticated() : Unauthenticated();
}
}
// State.
abstract class AuthenticationState {}
class Authenticated extends AuthenticationState {}
class Unauthenticated extends AuthenticationState {}
If you test the app you will see state management works between login and home page as expected with the code, but as soon as you navigate to the second screen form the home page and press the logout button, state is changed on the App widged but widget tree is not updated.
The problem is in your onGenerateRoute you are always redirecting to the HomeScreen in your else case. The home property on MaterialApp is only called when the app first opens. So, to truly fix your problem (I saw this commit in your repo, which is a workaround that won't work if your user's session were invalidated externally), you should add something like the following:
home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/second')
return MaterialPageRoute(builder: (_) => SecondScreen());
else
return MaterialPageRoute(builder: (_) => state is Unauthenticated ? LoginScreen() : HomeScreen());
},
I think that should work for you.
I need a dynamic "back" button on every screen/page that also shows the title of the previous screen.
Navigation is done via global navigatorKey, pushing new routes is not done from a specific screen.
Is this possible with built-in navigator or it needs to be built from scratch?
Just pass the string you want to display to the constructor of the widget which will be your next page.
Example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return Sec('title1');
}));
},
child: Text('Press here'))));
}
}
Widget backButton(context, t) {
return Row(children: [
Expanded(
child: IconButton(
icon: Icon(Icons.backspace),
onPressed: () => Navigator.pop(context),
),
),
Text(t,style:TextStyle(fontSize:16))
]);
}
class Sec extends StatelessWidget {
String t;
Sec(String x) {
t = x;
}
Widget build(context) {
return Scaffold(
appBar: AppBar(leading: backButton(context, t)),
body: Center(
child: FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Press here to go back'))));
}
}
Output
I am trying to move signup screen to OTP screen. From Signup screen I need to pass email id to OTP screen. Now, I am using below, but cannot resolve the arguments: parameter in that.
Navigator.pushNamed(context, Routes.ROUTE_OTP,arguments:{"id": 'email'});
Searched so many tutorials they given there to use arguments:( but my bad I can't find:(
A simple example demonstrating your requirement follows:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (settings) {
WidgetBuilder builder;
Map arguments = settings.arguments;
switch (settings.name) {
case '/':
builder = (
BuildContext _,
) =>
SignUp();
break;
case '/otp':
builder = (
BuildContext _,
) =>
Otp(id: arguments["id"]);
break;
default:
return null;
}
return MaterialPageRoute(builder: builder, settings: settings);
},
);
}
}
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Signup"),
),
body: Center(
child: FlatButton(
onPressed: () {
Navigator.of(context).pushNamed(
"/otp",
arguments: {
"id": "email#email.com",
},
);
},
child: Text("SEND OTP")),
),
);
}
}
class Otp extends StatelessWidget {
final String id;
Otp({this.id});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("OTP"),
),
body: Center(
child: Text(id),
),
);
}
}