My application has a bottom navigation bar, with 2 pages in the menu.
On page 1, I can fill out a form and it calculates me values that it displays to me by pushing in a 1.1 page.
On this page I have a button that allows me to redirect me to page 2 as if I clicked menu 2 of the navigation bar.
This works. My problem is how to send the data from my page 1.1 to this page 2.
The goal being that my page 2 is a form which is empty if I call it by the navigation bar but which is filled automatically if I pass by the page 1.1 in focus of the calculated values.
Here an exemple of the redirection that I do:
Here is my code :
my_app.dart :
final ThemeData _AppTheme = AppTheme().data;
final navBarGlobalKey = GlobalKey(); // => This is my key for redirect page
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp>{
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'App',
home: MyBottomNavigationBar(),
theme: _AppTheme,
navigatorKey: locator<NavigationService>().navigatorKey,
onGenerateRoute: Router.generateRoute,
initialRoute: HOME_ROUTE,
);
}
}
My bottom_navigation_bar.dart :
class MyBottomNavigationBar extends StatefulWidget
{
MyBottomNavigationBar({Key key}) : super(key: key);
#override
_MyBottomNavigationBarState createState() => _MyBottomNavigationBarState();
}
class _MyBottomNavigationBarState extends State<MyBottomNavigationBar>
{
int _pageIndex = 0;
final List<Widget> _pagesOption = [
page1.1(), // => Here I load direclty my page 1.1 with data for the exemple
page2(),
];
void onTappedBar(int index)
{
setState(() {
_pageIndex = index;
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child : Scaffold(
body : _pagesOption.elementAt(_pageIndex),
bottomNavigationBar: BottomNavigationBar(
key: navBarGlobalKey,
currentIndex: _pageIndex,
onTap: onTappedBar,
type: BottomNavigationBarType.fixed,
items : [
BottomNavigationBarItem(
icon : Icon(Icons.home),
title : Text('Home')
),
BottomNavigationBarItem(
icon : Icon(Icons.settings),
title : Text('Setting')
),
]
),
)
);
}
}
And here my widget submit button of page 1.1 :
Widget _getSubmitButton(){
return RaisedButton(
child: Text(
'Send'
),
onPressed: () {
final BottomNavigationBar navigationBar = navBarGlobalKey.currentWidget;
navigationBar.onTap(1); // => How to send data that I have in my page ???
},
);
}
For this, you can use Shared Preferences, the main idea is that:
Store the value of the calculated value in SharedPref from Page 1 when you're passing to Page 1.1
Let you checks for the value by default in Page 2's initState(), any changes in the Shared Preferences will be fetched in the Page 2 itself, using SharedPref get method.
WHY?
This is probably a cleaner way to achieve what you want, since in the BottomNavigationBar will not help you do this, but a Shared Preferences value will always give you that data which you can use it any time
Let's see how you can achieve this:
PAGE ONE
// Set the data of the form here
class _PageOneState extends State<PageOne>
{
void onSubmit() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
//make sure you store the calculated value, if that is String
// use setString() or if it is an int, setInt()
// and then pass it to the SharedPref
// key is a string name, which is used to access
// key and store, so choose the name wisely
await prefs.setInt("key", your_calculated_value);
}
}
PAGE TWO
class _PageTwoState extends State<PageTwo>
{
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
// This will be responsible for getting the result from SharedPref
int calculated_value;
#override
void initState(){
super.initState();
// get your list here
calculated_value = _prefs.then((SharedPreferences prefs){
// here if no data is then _values will have 0
// which you can use it to check and populate data
return (prefs.getInt("key") ?? 0);
});
}
}
This is the most reasonable way of doing the thing which you want. In this manner, whenever, PageTwo will trace any values, it will reflect, else, your choice for 0 check result. Let me know, if you have any doubts in that.
In your FirstActivity
onPressed: () {
navigatePush(SecondActivity(text: "Data"));
}
In your SecondActivity
class SecondActivity extends StatefulWidget {
String text;
SecondActivity({this.text});
}
You can pass the the values as arguments when you push to your new screen. This could get messy if you're building a larger project.
A cleaner implementation would be to use a Provider. Set up the data you want in a model mixed in with ChangeNotifier and use Provider.of<*name of your class*>(context) where ever you need to use it.
Related
I'm dealing with back actions. I'm not able to achieve good results with WillPopScope() (it's only called with top app return button but not called with android back button).
In my app, I have several pages and when android back button is pressed, I don't see the previous page.
For example, I have main-page1-page2-page3
If I'm in page 3 and press Android back button I return to page1, not to page2. In other cases it returns to main...How it is possible? Is there a way to define de pages "order"?
EDIT
This is my code that I shared in a previous question.
class Calendario1 extends StatelessWidget {
final List listaini;
Calendario1(this.listaini);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "ATGapp",
home:Cal1(listaini: listaini),
);
}
}
class Cal1 extends StatefulWidget {
final List listaini;
Cal1({Key? key,required this.listaini}) : super(key: key);
#override
///
_Cal1State createState() => _Cal1State();
}
class _Cal1State extends State<Cal1> {
#override
void initState() {
getImage(path1);
super.initState();
}
String url_1 = '';
getImage(String path1) async {
//String url='';
final ref1 = FirebaseStorage.instance.ref().child(path1);
var url1 = await ref1.getDownloadURL();
setState(() => url_1 = url1);
}
final FirebaseStorage storage =
FirebaseStorage.instance;
String path1 = 'fondos/mons.jpeg';
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async{
print('Bck button pressed');
return false;
},
child: Scaffold(
body: Column(...//and so on
add Navigator.pop() inside onWillPop and return true.
onWillPop: () async {
// do something here
return true;
},
return new WillPopScope(
onWillPop: _willPopCallback,
child: new Scaffold(
//then the rest of your code...
Thank your for all your help.
The final solution has been a new structure of the app pages. Until now, every page was a different dart file and with this structure, the behaviour of willpopscope and any other solutions didn't work for me.
Now, all pages are in the same file ordered by routes definitions in MaterialApp. Now, I'm able to catch the android back button click and work with it.
I hope this can be helpful for others.
I want to change the value of an integer in another class and rebuild this class so the bottom navigation bar items will change according to the integer
Here's the main class were the bottom navigation bar exists and the condition to hide or show the items :
class Home extends StatefulWidget {
static int showCard = 0;
#override
_HomeState createState() => _HomeState();
}
#override
Widget build(BuildContext context) {
...
//the items to show and hide according to int showcard
if (Home.showCard == 0)
BottomNavigationBarItem(
icon: Icon(
Icons.person_outlined,
),
title: Text(
'Profile',
),
),
if (Home.showCard != 0)
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text(
'Settings',
),
)
And here's the second class where i want to change the value of showcard to hide the profile section ad show the setting , i'm calling set state with on pressed in this class it's changing the value but not rebuilding the main class :
class Homehome extends StatefulWidget {
const Homehome({Key? key}) : super(key: key);
#override
_HomehomeState createState() => _HomehomeState();
}
class _HomehomeState extends State<Homehome> {
#override
Widget build(BuildContext context) {
...
child: RaisedButton(
onPressed: () {
setState(() {
Home.showCard = 1;
});
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) => Green(),
));
},
any suggestions ?
You can use callback functions:
have a look at this solution.
https://stackoverflow.com/a/59832932/16479524
upvote if helpful :)
You don't need to change the state. You should only update the state when you want to change something inside a widget, for instance a Text value or something like that.
Every time you change a value, it changes. If you want to change a 'visible value', then you have to change the state by setState method.
It sounds like you need to get a better understanding of state.
Widgets in Flutter live in a hierarchy. You should have a state in one widget, and then child widgets update based on this state. You can pass state changes down to child widgets using parameters.
Also, prefer to use proper data types for the situation. Don’t use a numerical data type like an int for showing or hiding a widget; use a bool (true or false).
I'm building a simple app with lots of nested widgets/classes from different specialised files
list of files:
main.dart -> the menu file used to start the activity
"Activity()"
group_widgets.dart -> the file that contains the custom widget
"CustomWidget()"
file_a.dart -> the file that uses the custom widgets
inside the "Activity()"
other.dart -> other files that needs to manage data changed in CustomWidget()
inside main.dart:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Activity(),
));
},
inside group_widgets.dart:
class CustomWidget extends StatefulWidget {
const CustomWidget({Key? key}) : super(key: key);
#override
State<CustomWidget> createState() => _CustomWidgetState();
}
class _CustomWidgetState extends State<CustomWidget> {
var _boolean = false;
bool switchBoolean(bool state) => !state;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => {
setState(() {
_boolean = switchBoolean(_boolean);
})
},
child: Container(
color: _boolean == true ? Colors.green : Colors.red,
),
);
}
}
inside file_a.dart
class Activity extends StatefulWidget {
const Activity({Key? key}) : super(key: key);
#override
State<Activity> createState() => _ActivityState();
}
class _ActivityState extends State<Activity> {
#override
Widget build(BuildContext context) {
bool boolean = true;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CustomWidget(),
Text('Here where to show the variable from CustomWidget'
'and prove I can retrieve it')
],
),
),
);
}
}
inside other.dart
if ( booleanFromCustomWidget == true) {
Something ...
}
What is the best practice to achieve it?
I've read a lot here but nothing seems to well fit my needing.
Just comment if my request is not as clear as it seems to me))
Please correct me if I am wrong, but if you want to access data from parent widgets from inside their descendants (children or even nested children) you can either pass them down via parameter arguments:
Child(int age, String name);
And then accept it in the new file, where the Child widget lives, via its constructor:
class Child {
String name;
int age;
// Constructor
Child(String passedName, int passedAge) {
this.name = passedName;
this.age = passedAge;
}
}
Inside the parent.dart you then have to import the children.dart to use it.
Or use a popular package like the provider package: https://pub.dev/packages/provider
This allows you to store data containers, which you can access basically anywhere in your code. Feel free to google it & watch some tutorials to get started, as it is the preferred approach to avoid passing data to widget which really do not care about the passed parameters.
Note: You can transfer the idea to output the String data like in your example code above.
you can use a state manager like provider, or bloc
At the top level, you set up the data services
I'm using navigator 2.0 and all is well with the default URL strategy ('#'). I have a singleton service that loads data and this singleton is called only once no matter the routes.
However, when I change the URL strategy either by including url_strategy: ^0.2.0 as a dependency or following the steps at https://flutter.dev/docs/development/ui/navigation/url-strategies, the app reloads, and my singleton instantiates all over (and the expensive load operations happen again).
Has anyone else experienced this or know a solution?
As an example, below loads with initial route of / and displays "the number is 0 (zero)." and the URL is updated to /0. Pressing the increment button shows everything in sync (URL and screen). Manually entering a new route works fine and the StateService global variable is instantiated just one (have a print line of "*** in StateService() constructor ***" to show this). But if you uncomment out "setPathUrlStrategy()" in function main() and then try again, you'll see that manually entering a new route causes the StateService to be instantiated all over again. This will obviously kill performance for apps doing work upon startup.
Any ideas?
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
void main() {
// setPathUrlStrategy(); // causes app to reload when URL manually changed; leaving out doesn't
runApp(MyApp());
}
// below sb a Singleton; will just instatiate a global variable once to simulate...
class StateService {
int number = 0;
List<String> words = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven"];
StateService() {
print("*** in StateService() constructor.... ***");
}
void increaseNumber() {
number++;
}
String getWord(int number) {
if (number < 0) return "unknown";
if (number > words.length - 1) return "unknown";
return words[number];
}
}
// display the number and its word (if 0-11); has button to increment value
class DisplayNumberScreen extends StatelessWidget {
final VoidCallback increase;
final int number;
DisplayNumberScreen({Key? key, required this.number, required this.increase}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text("The number is: $number (${globalState.getWord(number)})")),
floatingActionButton: FloatingActionButton(
onPressed: increase,
tooltip: "Add 1",
child: Icon(Icons.add),
),
);
}
}
class NavigationState {
final int value;
NavigationState({required this.value});
}
class UrlHandlerRouterDelegate extends RouterDelegate<NavigationState> with ChangeNotifier, PopNavigatorRouterDelegateMixin {
#override
GlobalKey<NavigatorState> get navigatorKey => urlHandlerRouterDelegateNavigatorKey;
void _increaseNumberHandler() {
globalState.increaseNumber();
notifyListeners();
}
// App state to Navigation state, triggered by notifyListeners()
#override
NavigationState get currentConfiguration => NavigationState(value: globalState.number);
#override
Widget build(BuildContext context) {
return Navigator(
pages: [
MaterialPage(child: DisplayNumberScreen(number: globalState.number, increase: _increaseNumberHandler)),
],
onPopPage: (_, __) {
return false;
},
);
}
// Navigation state to app state
#override
Future<void> setNewRoutePath(NavigationState navigationState) async {
globalState.number = navigationState.value;
}
}
class UrlHandlerInformationParser extends RouteInformationParser<NavigationState> {
// URL to navigation state; note initial route of '/' will result in 0 as will any route that's not a number
#override
Future<NavigationState> parseRouteInformation(RouteInformation routeInformation) async =>
NavigationState(value: int.tryParse(routeInformation.location!.substring(1)) ?? 0); // substring(1) to remove the leading '/'
// Navigation state to URL
#override
RouteInformation restoreRouteInformation(NavigationState navigationState) => RouteInformation(location: '/${navigationState.value}');
}
// Assign global variables
final GlobalKey<NavigatorState> urlHandlerRouterDelegateNavigatorKey = GlobalKey<NavigatorState>();
final globalState = StateService();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: UrlHandlerRouterDelegate(),
routeInformationParser: UrlHandlerInformationParser(),
);
}
}
I am trying to use a custom statefull PageWrapper widget to wrap all my pages. The idea is to make it return a Scaffold and use the same menu drawer and bottom navigation bar, and call the appropriate page as page parameter.
My bottomNavigationBar is working well and I am setting the correct selectedIndex, but I can't find a way to access it in the child page (that is in another file), since I don't know how to access the parent's selectedIndex and display the appropriate widget from my page's list.
import 'package:flutter/material.dart';
class PageWrapper extends StatefulWidget {
final Widget page;
final AppBar appBar;
final BottomNavigationBar bottomNav;
final Color bckColor;
PageWrapper({#required this.page, this.appBar, this.bckColor, this.bottomNav});
#override
_PageWrapperState createState() => _PageWrapperState();
}
class _PageWrapperState extends State<PageWrapper> {
int _selectedIndex;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
void initState() {
super.initState();
_selectedIndex = 0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.appBar,
backgroundColor: widget.bckColor,
bottomNavigationBar: CustomBottomNavigation(selectedIndex: _selectedIndex, onItemTapped: _onItemTapped),
body: widget.page,
drawer: Drawer(...),
);
}
}
Named roots in my main.dart:
home: PageWrapper(page: HomeScreen()),
routes: {
'form': (context) => PageWrapper(page: RoomService()),
},
I would like to access that bottom navigation bar's current index somehow in my HomeScreen and RoomService screen. Is there a way to do it?
You can solve that by using a State Management tool like Provider or Bloc. To keep things simple, lets use Provider to do it.
Wrap MaterialApp with a ChangeNotifierProvider in your main.dart.
return MultiProvider(
providers: [
ChangeNotifierProvider<IndexModel>(
create: (context) => IndexModel()),
],
child: MaterialApp(...)
);
Create a model that will hold your index value:
Also, you have to override the getter and setter of index in order to call notifyListeners after its value is set. Here is an example:
class IndexModel extends ChangeNotifier {
int _index;
get index => _index;
set index(int index) {
_index = index;
notifyListeners(); //Notifies its listeners that the value has changed
}
}
Here is how you can display your data according to its index (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value it is listening to, changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<IndexModel, String>(
selector: (_, model) => model.index,
builder: (_, i, __) {
switch(i){
//do your returning here based on the index
}
},
);
}
)
}
Extra note. Here is how you can access the values of ImageModel in your UI:
final model=Provider.of<IndexModel>(context,listen:false);
int index =model.index; //get index value
model.index=index; //set your index value
You have to pass listen:false when you aren't listening for changes. This is needed when you are accessing it in initState or in onPressed.