Save values of variables while navigation in Flutter - flutter

I am new to Flutter and I am trying to save the value of "counter" on first_screen when I navigate to second_screen and after that I want to save the value of "secondCounter" on second_screen when I navigate to first_screen. The "counter" and "secondCounter" value resets to 0 when I navigate between the two screens but I want to save the values of them. My code is as follows :
main.dart :-
import 'package:flutter/material.dart';
import 'package:provider_practice/screens/first_screen.dart';
import 'package:provider_practice/screens/second_screen.dart';
void main() {
runApp(MaterialApp(
home: FirstScreen(),
routes: {
"/first" : (context) => FirstScreen(),
"/second" : (context) => SecondScreen(),
},
));
}
first_screen :-
import 'package:flutter/material.dart';
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You pressed the button $counter times."),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
setState(() {
counter++;
});
},
child: Text("Click Me"),
),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/second");
},
child: Text("Go to Second"),
),
],
),
),
),
);
}
}
second_screen.dart :-
import 'package:flutter/material.dart';
class SecondScreen extends StatefulWidget {
#override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
int secondCounter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You pressed the button $secondCounter times."),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
setState(() {
secondCounter++;
});
},
child: Text("Click Me"),
),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
Navigator.pushNamed(context, "/first");
//Navigator.pop(context);
},
child: Text("Go to First"),
),
],
),
),
),
);
}
}

I am not sure if you specifically you need it to reset when the app relaunches or not but if it is fine if the value is preserved when you relaunch the app then here are a few options. Either way, you can reset the value when the app launches manually by setting it back to 0.
The first and simplest way is to use the answer in this comment. If you have both widgets in an IndexedStack (read more here) and then have the button change the stack index that would work but you would lose the benefit of page transition animations and this is a less performant option as your app grows because flutter has to run both widgets at the same time even if one isn't being used.
A second more performant way you can do this is through the Shared Preferences package. This would save it to the disk so you would need to reset it every time you launch the app if you want it to be 0 every time you open the app.
A third way is to use an external database such as Firebase. Firebase offers both their "Realtime Database" and their newer "Cloud Firestore" as well as their authentication services all for free so it might be an option you want to look into for building apps in the future. I would recommend Firestore over the real time database because it is newer and I prefer it personally. This option would also need you to reset the counter when launching the app but that shouldn't be too big of a problem.
Another way you can do this (this won't preserve state when relaunching the app) is to use the Provider Package. This package was endorsed by Flutter and is the recommended way to manage state. If you add a provider at the root of your app then it will be preserved and it can store both the first and second counter for you. Provider has a bit of a learning curve so I would recommend you look into it a bit.
Here are two videos which helped me get started with Provider:
https://youtu.be/O71rYKcxUgA
https://youtu.be/MkFjtCov62g
I'd recommend you watch them both as they are by the same person and one is an introduction to what Provider is and the other shows you how to use it. The second video has a similar example to your use case but I'd recommend you still watch both.
Hope this helps. Please let me know if this answered your question or if you need any more help or clarification please let me know.

This is easy to implement and there are a few ways you can do it.
One way is to pass it in as a parameter.
If you add the counter variable to be inside of the FirstScreen/SecondScreen widgets, you can then add them to the constructor.
Example:
class FirstScreen extends StatefulWidget {
int counter;
FirstScreen(counter);
#override
_FirstScreenState createState() => _FirstScreenState();
}
Then in your state's body you would change the text to Text("You pressed the button ${widget.counter} times.") and the setState function to setState(() {widget.counter++;});
You would do the same in the second widget making a parameter called counter or whatever you want and then make a constructor. You can also make it required or set it to have a default of 0 if it is not passed through.
Finally, to pass it through to the second widget you can just use Navigator.push(context, SecondScreen(widget.counter) and vice versa. This however, won't let you use named routes.
Another approach is to use arguments and named routes. I think this will suit your use case better.
In both of your screens where you navigate, just add an arguments parameter and pass in the counter Navigator.pushNamed(context, 'routePath', arguments: counter);. (P.S. You don't have to name the counters as firstCounter and secondCounter, they can both be called counter since they are in different widgets). Then just add to both widgets counter = ModalRoute.of(context).arguments. You also don't need to wrap your counter value in curly braces ({}). In the vide he needed the data as a map so he did that, but you just want a number. Hope this helps.
Here is a video I found which explains how to pass arguments in named routes if you find the text confusing. For context, this is a video series teaching Flutter and the app he is currently building is a world time app. Video Link.
If you are interested in the entire course here is the Video Playlist

Related

loading a page in background in Flutter

I need to load some data in a global variable in Flutter every minute.
To load the data (which is a list) to my global data, I need to open another page. But how can I open that page in the background because I do not need that page, I just want to get some data from it.
create a different class and write a function that instantiates a list from itself
Loading a Page is something related to UI or Presentation layer but loading your data is related to Business layer, you need to keep separate them from each other
There is a topic known as State Management, you should centralize your data providers to a separate layer and change your Presentation layer based on the State of your data
First of all take a look at this link, here is an example of using Provider pattern to manage different State of your data
Then you can use some more complicated libraries like BLOC library for State Management
(More of a workaround)
As I wrote here one option is to use Stack widget as a page loader.
Each "page" expand on the entire screen.
When you want to show the next "page" replace the front layer in the stack with SizedBox.
So all the elements are actually randerd at the same time but will not be visible.
For example, the video on the "second page" will start getting loaded even when the user is on the "first page" and will be ready for the user when he continues.
One way to do that is by using get as state management.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PageWithLayers extends StatelessWidget {
const PageWithLayers({super.key});
#override
Widget build(BuildContext context) {
final TestController c = Get.put(TestController());
return Stack(
children: [
Container(
color: Colors.red,
child: FutureBuilder<Widget>(
future: Future(() async {
await Future.delayed(const Duration(seconds: 4));
return const Text('Done loading in the background');
}),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return snapshot.requireData;
}
return const Text('Loading Video');
},
),
),
Obx(
() => c.toShowTopLayer > 0
? Container(
color: Colors.blue,
width: double.infinity,
height: double.infinity,
child: Center(
child: TextButton(
onPressed: c.removeTopLayer,
child: const Text('Next'),
),
),
)
: const SizedBox(),
),
],
);
}
}
class TestController extends GetxController {
var toShowTopLayer = 1.obs;
removeTopLayer() => toShowTopLayer--;
}

ScaffoldMessenger.of(context).showSnackBar() doesn't work when called directly inside a widget build function

While looking for examples for using flutter SnackBar with ScaffoldMessenger, I only found usages with onPressed function:
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: ...,
onPressed: () => _showToast(context),
));
}
void _showToast(BuildContext context) {
final scaffold = ScaffoldMessenger.of(context);
scaffold.showSnackBar(
SnackBar(content: const Text('Added to favorite')),
);
}
}
but non of the examples explain why one have to use it with onPressed only,
so I tried it my self and used in directly inside the build function:
Widget build(BuildContext context) {
_showToast(context); <---
return Container(
child: ElevatedButton(
...,
));
}
but than I got this error:
and I am not sure why this it happening.
I ended up giving Scaffold a key and using the GlobalKey<ScaffoldState> approach, but I really want to understand why using ScaffoldMessenger.of(context).showSnackBar() without a callback\onPressed didn't work
while ScaffoldMessenger.of(context) we need to give some time to be rendered properly(for full Scaffold). we can know the time from addPostFrameCallback. Also if you try to show the middle of widgets, the 1st portion will render but rest widgets will face this issue.
here is the soulution on build method.
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
_showToast(context);
});
to understand the concept properly we can do something like this.
Scaffold(
body: Container(
height: 100,
color: Colors.cyanAccent,
child: Column(
children: [
Text("This text will render "),
Builder(builder: (context) {
_showToast(context);
///but you will be able to see Message
return Text("it will cause error");
}),
Text("This text will not render "),
],
),
),
);
Try to add or declare your snackbar inside Future.deleayd
Future.delayed(Duration.zero, () async {
yourSnackBarFunction();
});
In Flutter build function is called by the framework itself whenever a widget needs to be rebuilt. It can occur many times!
On the other hand, you usually want to display a toast / snackbar when you want to inform your user about something. So you can display a snackbar from onPressed and many other places, but the build function itself is only for building a widget, and not responding directly to user actions.

Does watch(provider) update children of parent widget?

I'm trying to update both Pages with one UserInteraction, therefore trying to access the same Stream in both Pages, with the Riverpod library.
Now to explain it further. When I pass the Stream to the CustomerPage I'm able to get the data (the String Anton). and when I click on the Button that triggers the change in FireStore, the String gets updated to "Marco" in the ParentWidget, when I go back to it. But it doesn't change in the CustomerPage unless I reopen the Page via the RaisedButton in the ParentWidget.
But I want it to update after I click the Button on the CustomerPage.
I hope this makes it clearer.
class ParentWidget extends ConsumerWidget{
Widget build(BuildContext context, ScopedReader watch){
Stream<DocumentSnapshot> doc = watch(streamProvider.stream);
return Container(
child: Column(
children: [
Text(doc.name), //Lets say the name is Anton,
RaisedButton(
child: Text(" road to CustomerPage"),
onPressed:(){
Navigator.of(context).pushNamed(RouteGenerator.customerPage, arguments: doc);
},), //RaisedButton
],), //Column
); //Container
}
}
class CustomerPage extends StatelessWidget{
Stream<DocumentSnapshot> docStream
CustomerPage({this.docStream});
Widget build(BuildContext context){
return Column(
children: [
Text(docStream.name) //Here is also Anton
RaisedButton(
child: Text("Change Name"),
onPressed: () {
context.read(streamProvider).changeName("Marco");
},), //RaisedButton
]
); //Column
}
}
On how I've understood so far is that, riverpod allows you to fetch the state of a provider, which basically is a value(?), that's why it's sufficient to just watch it in any Widget you want to access it's data from. There is no need anymore (just speaking for my case), to let Widgets pass the around in the App.
Down below is the solution which i believe to be right.
It also doesn't matter on how many times I call the provider. It's always going to be same Instance. For my case it means, that doc and doc2 are the same.
I hope this makes it clearer.
class ParentWidget extends ConsumerWidget{
Widget build(BuildContext context, ScopedReader watch){
Stream<DocumentSnapshot> doc = watch(streamProvider.stream);
return Container(
child: Column(
children: [
Text(doc.name), //Lets say the name is Anton,
RaisedButton(
child: Text(" road to CustomerPage"),
onPressed:(){
Navigator.of(context).pushNamed(RouteGenerator.customerPage);
},), //RaisedButton
],), //Column
); //Container
}
}
class CustomerPage extends ConsumerWidget{
Widget build(BuildContext context, ScopedReader watch){
Stream<DocumentSnapshot> doc2 = watch(streamProvider.stream);
return Column(
children: [
Text(doc2.name) //Here is also Anton
RaisedButton(
child: Text("Change Name"),
onPressed: () {
context.read(streamProvider).changeName("Marco");
},), //RaisedButton
]
); //Column
}
}

Flutter - what is needed to reload a network image when URL doesn't change

I'm just starting to learn app development with Flutter.
I am trying to build an app for learning purposes that grabs an image from a website using their API to get a random image. The app displays the image, and has a button which is supposed to grab a new random image when pressed.
The problem is that the URL for the API doesn't change, so Flutter thinks the image is the same and doesn't download a new image. At least, that's what I think is going on.
The code is very basic. So far just showing an image and a button. I've tried various things, and was thinking imageCache.clear(); should do the job when the button is pushed, but nothing seems to work. I have also tried various tricks with resetting state and trying to navigate to the same page with named routes, but they all still show the same image (which is why I think it might be a caching issue). :shrug:
All of this is running from a single main.dart file.
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
backgroundColor: Colors.lightGreen[50],
appBar: AppBar(
title: Text("Show Me The Lettuce",
style: TextStyle(color: Colors.white70)),
backgroundColor: Colors.green[900],
),
body: ShowLettuce(),
),
),
);
}
class ShowLettuce extends StatefulWidget {
#override
_ShowLettuceState createState() => _ShowLettuceState();
}
class _ShowLettuceState extends State<ShowLettuce> {
String url = 'https://source.unsplash.com/900x900/?lettuce';
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(80.0),
child: Center(child: CircularProgressIndicator()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage, image: url),
),
],
),
SizedBox(
height: 20.0,
),
RaisedButton(
onPressed: () {
imageCache.clear();
print('button pressed.');
},
child: Text('Show Me A New Lettuce!'),
),
],
),
);
}
}
I've read through a lot of Flutter documentation, but I'm not yet at the point where I can interpret much of what they say into usable code (not sure where and how I'm supposed to plug it into my code, for example the imageCache documentation: https://api.flutter.dev/flutter/painting/ImageCache-class.html). It seems like this should be a really easy solution, but it is escaping me.
Any help is much appreciated.
I have solved you problem.
You must add for your code couple lines code:
as first - import:
import 'dart:math';
next - in class _ShowLettuceState after declared url variable:
var random = new Random();
and for finish - after click the button, namely in onPressed:
setState(() {
url = 'https://source.unsplash.com/900x900/?lettuce' + random.nextInt(100).toString();
});
I suggest you use this code :
String url = 'https://source.unsplash.com/900x900/?lettuce-${Random().nextInt(100)}';
This is inspired by #Captivity solution, but I add a - that makes it work.

How to align widget to another widget in Flutter?

I have a RaisedButton widget inside of a Center widget as one of the widgets in a Column of widgets. I want to add a CircularProgressIndicator to the right side of this button and show it when the button is pressed. Yet I want to leave the button centred when the progress bar is shown. In other words I want the button always be in the center and the progress bar aligned to this button.
I tried to use a Row here but this pushes the button and it becomes not centred any more.
EDIT1: Looking at the result of the solution provided by #Anil Chauhan (thanks for the answer):
Like I said before that I tried to use Row like he did, the problem is that in this case the button is not in the centred in the screen and is pushed by the progress bar. And I need it to stay in the middle of it's row.
EDIT2: #Anil Chauhan edited answer now works for a specific case in which the button is predetermined size. But if the size of the button is changed based on the language of the text (in apps that have several languages) this solution will not work.
This is the reason the question I asked is: "How to align widget to another widget". Because if I could that I don't have to worry about the button text size any more.
What would be the right way to handle this in Flutter?
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
bool _showIndicator = false;
void _onButtonClicked() {
setState(() {
_showIndicator = !_showIndicator;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: <Widget>[
const Expanded(child: SizedBox()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: RaisedButton(
child: Text("I am Too Big"),
onPressed: _onButtonClicked,
),
),
Expanded(
child: _showIndicator
? const Align(
alignment: Alignment.centerLeft,
child: CircularProgressIndicator(),
)
: const SizedBox(),
),
],
),
),
);
}
}
Here is my explanation:
The RaisedButton size is depends on its child. If you add it to Row it will automatically align to left(or start).
Expanded widget will fill the remaining space in Flex widget(Row & Column are child classes of Flex). If you add more than one Expanded widgets, it will split equally. So I added two Expanded to both the side of button to make it center.
Now We should give child for Expanded Widget.
For the first one(left) we don't have anything to display so I added SizedBox.
For the second one(right) we need CircularProgressIndicator. so I added it.
The Expanded widget will try to make its child to fill the space inside of it. So the CircularProgressIndicator will become Ellipse shaped. We can avoid this by using Align Widget.
Try this:
Updated:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyAppOne(),
);
}
}
class MyAppOne extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyAppOne>{
bool show = false;
#override
Widget build(BuildContext context){
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
alignment: Alignment.center,
child: RaisedButton(
onPressed: () {
setState(() {
show =!show;
});
},
child: Text('Show'),
),
),
Positioned(
right: MediaQuery.of(context).size.width * .20,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10.0),
child: show ? CircularProgressIndicator() : Container(),
),
)
],
)
);
}
}
Flutter's Column and Row widgets have two convenient properties called mainAxisAlignment and crossAxisAlignment. I assume since you're using a Column and want the CircularProgressIndicator to the right of the button, you might be want to use crossAxisAlignment since the cross-axis of a Column is along the horizontal.
If possible, please share your code for better understanding and support of the issue.