Flutter app variables reset each time you leave the page - flutter

I'm making a flutter app. Each time I leave the page, the variables reset. How do I make it so that the data stays the same, even if you leave the page? I'm using getx for state management. Here is my code:
//Button that leaves the page
Align(
alignment: Alignment.topLeft,
child: IconButton(
onPressed: () => Get.back(),
icon: const Icon(Icons.arrow_back),
),
),
When you click the button, it leaves the page and resets the variable maintasks shown here.
Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text(
maintasks,
style: const TextStyle(
fontSize: 23,
color: Colors.black,
fontWeight: FontWeight.w800,
),
),
),
),
There is a function which changes the variable maintasks to the user input. However this user input is reset each time you leave the page. I've tried using SetState to save the variable but for some reason it doesn't work.
Sorry if my question is worded badly. If anyone knows how to do this, please tell me. Thanks in advance.

Widget state is reset as soon as the widget is disposed.
For this, using GetX, you could use GetXControllers
Example, let's imagine you need a state that's composed by a username and a password,
class LoginController extends GetxController {//GetxController comes from Getx package
var username = ''.obs;//creates a stream that can be listened from the UI, whose value is a String
var password = ''.obs;//same here
void onUsernameChanged(String newValue) {
username(newValue);//changes username stream last value to newValue's
}
void onPasswordChanged(String newValue) {
password(newValue);//the same
}
}
Now, you must insert an instance of this controller in your widget tree before the widget is built (For example, in initState)
#override
void initState() {
_controller = Get.put<LoginController>(LoginController());
super.initState();
}
Once you know you have this instance in the widget tree, you just listen to its streams using GetX widget
GetX<LoginController>(
builder: (controller)=>Text(controller.username.value)
)//this guy will repaint every time username changes
This is important because the instance of this widget will remain alive in the widget tree until either the app ends or the controller is explicitly deleted

Related

Save values of variables while navigation in 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

How to pass text and images through multiple pages and preview in the final page in flutter?

I have 4 CreatePoll pages for my polling app.
CreatePoll1 is to add the questions and the options
CreatePoll2 is to add the caption, duration and category
CreatePoll3 is to add the images
CreatePoll4 is to preview all the details in the form of a card view, as it will look once it is posted.
Images: CreatePoll1 CretePoll2 CreatePoll3 CreatePoll4
How do I pass the data from all these CreatePoll pages and finally display it in the last preview page?
CreatePoll1 PageRoute:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreatePoll2(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
CreatePoll2 Page Route:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreatePoll3(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
CreatePoll3 Page Route:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreatePoll4(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
CreatePoll4 Page Route:
MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Nav(),
),
);
},
color: Colors.black,
textColor: Colors.white,
child: Icon(
Icons.arrow_forward,
size: 24,
),
padding: EdgeInsets.all(16),
shape: CircleBorder(),
),
Complete Code for CreatePoll Pages:
https://docs.google.com/document/d/1Nz7Zk1PFzE3y4Zo_QbJoGTImrm_Q9AYHVvgy3KEtfYo/edit?usp=sharing
Update after looking at code:
I'll give you an example of doing this with a string value using GetX State Management. This can be done with any state management solution but I find this to be the fastest and easiest to use and I suggest checking it out if you're not familiar with it. Either way, its in your benefit to learn at least one of the solutions so that this type of stuff will be easier for you going forward. Provider or its updated version Riverpod would be another good one to start with.
Using this solution all your pages can become stateless because the text editing controllers are being initialized in in the PollDataController class.
For every unique textfield you have, create a dedicated text editing controller for it in the PollDataController (GetX Controller class) class below. Every value you need to display needs to be a variable in the PollDataController class. The example below is an RxString that listens to the text editing controller and its value is updated whenever the textfield is changed.
class PollDataController extends GetxController {
TextEditingController nameController;
RxString name = ''.obs; // adding .obs makes this a stream based observable String
// this onInit override can be used instead of initState in a stateful widget.
// All text editing controllers can be initialized here
#override
void onInit() {
super.onInit();
nameController = TextEditingController();
nameController.addListener(() {
name.value = nameController.text; // the RxString is automatically updated with whatever is input in the textfield
});
}
#override
void onClose() {
super.onClose();
nameController.dispose();
}
}
Initialize your GetX controller, this can be done on a page or on the start of your app, but it needs to be done before trying to use the controller so for simplicity, put this in your main method before running the app.
Get.put(PollDataController());
Then access variables in the GetX class from anywhere in the app by "finding" the initialized controller.
final pollDataController = Get.find<PollDataController>();
To access the already initialized text editing controller from that class:
final _nameController = pollDataController.nameController;
Then when you want to display the updated value in your UI, there are a few different ways to do this with GetX. Wrapping your text widget in an Obx widget is one of the ways. This widget will automatically be rebuilt anytime the value of the variable changes.
Obx(()=> Text(pollDataController.name.value)); // use .value at the end of the variable to access the actual value of the RxString
So whats happening here is anywhere you use that text editing controller in the textfield, the RxString name is updated with that value and you can access it from anywhere in the app using the method above.
So this will still require some work on your end but I gave you an example of doing this with a string, but all basic data types are supported ie RxInt, RxList etc...
Any piece of data you want to display on the last page can be accessed via the pollDataController.
When it comes to images, looks like you're using an external package, but generally I manage dynamic images with AssetImage() and the assetName (path) is just another RxString.
Let me know if you have any further questions.
Original Answer:
You’ll need to post more code than just your buttons if you want someone to write up a potential solution for you.
But to add to what #Agreensh said: The idea is you pick a state management solution, and each CreatePoll page populates the data on the separate state management class outside of the UI, without passing arguments from page to page.
Then by the time you get to your last page, the data is already populated and you just need to display it from the state management class. If you post the full code from your pages to show what’s going on with your text fields and drop down lists etc...I can try and help you out.

How do I pass data to a widget in Flutter?

I have a json file containing a list of titles and songNumbers. I made a listview and used onTap and Navigator.push to send det data to a detail page. It works fine to display the data as text, but I want to play songs from local assets. I use audioplayers package and if I hardcode a songname it works, but I want to pass the songname into the play function to play the selected song. My code for det detail page look (a bit simplified and shortened) like this:
import 'package:flutter/material.dart';
import 'package:audioplayers/audio_cache.dart';
class SongDetail extends StatelessWidget {
final String title;
final String songNumber;
final player = AudioCache();
SongDetail(this.title, this.songNumber);
#override
Widget build(BuildContext context) {
Widget titleSection = Container(
decoration: BoxDecoration(
color: Colors.blueGrey[50],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 20.0),
child: IconButton(icon: Icon(
Icons.play_circle_filled,
size: 40.0),
onPressed: () { player.play('1.mp3'); },
),
),
I can use Text(this.title) and Text(this.songNumber) to display the title and songNumber as text. But how to get the data from songNumber into the onPressed function instead of the hardcoded '1.mp3'?
Bear in mind that I am a newbie who maybe has taken a to difficult task, but I like challenges :)
Have you tried this?
onPressed: () { player.play('$songNumber.mp3'); },

StatefulWidget using BlocBuilder doesn't rebuild on state change

I'm using Flutter (BloC pattern) to build a widget that streams multiple camera feeds. When a feed is clicked on, it is shown in the main feed (think Google hangouts, where the face of whoever is speaking is shown front and centre, and others are smaller at the side).
When trying to switch the selected feed, the state (int) gets yielded with the expected new value by the Bloc's MapEventToState; however, the main CamFeed widget doesn't switch feeds.
My understanding of this usage of a StatefulWidget that returns a BlocBuilder<CamSwitcherBloc, int> should rebuild when that int is changed. Instead, nothing seems to be happening. build() is only getting called when it first gets created, and when the state of one of the children within CamFeed is getting updated.
I've confirmed via observatory that there is, as expected, only one instance of the CamSwitcherBloc.
So - am I wrong in thinking that:
When MapEventToState inside my Bloc yields a new value (selectedCam, type in the builder below), build() should be called for my widget.
If that is correct, any suggestions as to where to continue my hunt would be greatly appreciated.
Thanks!
class CamSwitcher extends StatefulWidget {
#override
_CamSwitcherState createState() => _CamSwitcherState();
}
class _CamSwitcherState extends State<CamSwitcher> {
#override
Widget build(BuildContext context) {
final camSwitcherBloc = BlocProvider.of<CamSwitcherBloc>(context);
return BlocBuilder<CamSwitcherBloc, int>(
builder: (context, selectedCam) => Stack(
children: <Widget>[
Container(
width: 1200,
height: 800,
child: CamFeed(
topic: camSwitcherBloc.cameras[selectedCam],
),
),
Row(
children: <Widget>[
Container(
width: 180,
height: 120,
child: CamFeed(
topic: CamSwitcherBloc.cam1,
focuser: () {
camSwitcherBloc.dispatch(ExpandCamera(0));
},
),
),
Container(
width: 180,
height: 120,
child: CamFeed(
topic: CamSwitcherBloc.cam2,
focuser: () {
camSwitcherBloc.dispatch(ExpandCamera(1));
},
),
),
],
),
],
),
);
}
}
Turns out this had to do with the CamFeed widget. Upon switching, it was indeed subscribing to the new feed, but wasn't unsubscribing from the old one. This is symptom of the way CamFeed works, and is not likely to be useful for other readers.
Perhaps relevant for others:
In this case, while CamSwitcherBloc was not rebuilding (though I expected it would), its child (Camfeed) actually was, as expected.

How to make the phone vibrate on long press with Flutter?

Is there a way to make the phone vibrates when the onLongPress "timer" is reached?
For example:
1- There is a list of items
2- I push and hold one item for a "long press"
3- When the onLongPress "timer" is reached, I want the phone vibrates just a little.
Doable?
Thanks
You can wrap your widget (item) that you want to long press in an GestureDetector for example, resulting in something like this:
class MyVibrateButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onLongPress: () => HapticFeedback.vibrate(),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(border: Border.all(width: 2.0)),
width: 100.0,
height: 50.0,
child: Text(
'My Item\nPress me',
textAlign: TextAlign.center,
),
),
),
);
}
}
Edit: I just found that you can use the Flutter included services.dart package that contains the HapticFeedback class which allows you to trigger different native haptic feedbacks on both platforms. For this example you can use the vibrate() method. Since you want it to vibrate just a little you may want to also try lightImpact() or mediumImpact()
. I’ve edited my previous answer accordingly.