Storing List Variable on change - flutter

I am learning Flutter currently and was making a personal finance app. I have the option to bookmark my guides and then view them on the bookmark tab. Right now, I am using a list to simply store names of guides and display them as list tiles.
The issue I am having is that whenever the bookmark list is updated WHILE the app is running, the Bookmarks page loads the right info but then when I close and restart the app, it goes back to it's initial state of being empty. How can I fix it so that the app saves bookmarked tabs?
main.dart
List<String> bookmarked = [];
String introInfo = """ <h1>Introduction!</h1>
<p><strong><em>NOTE: The guides are U.S. specific but most information can be applied in most countries outside the U.S.</em></strong></p>
<p>The guides in this app will teach you the basics of personal finance.</p>
<p>Financial knowledge is something that is invaluable but the U.S. education system does not put much emphasis on it. If you are looking to get into personal finance, you're at the right place.</p>""";
void main() {
runApp(MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => MyApp(),
'/finTable': (context) => FinNav(),
'/disclaimer': (context) => Disclaimer(),
'/intro': (context) => GuideStyle(guideName: 'introduction',guideInfo: introInfo, isFav: bookmarked.contains('introduction'),),
'/budget': (context) => GuideStyle(guideName: 'budget',guideInfo: introInfo, isFav: bookmarked.contains('budget'),),
'/bookmark': (context) => Bookmarks(),
},
theme: ThemeData(fontFamily: 'Raleway'),
));
}
/* I have a stateless widget that shows all pages and navigates to one the user selects */
guidestyle.dart
class GuideStyle extends StatelessWidget {
String guideName;
String guideInfo;
Widget previous;
Widget next;
bool isFav;
GuideStyle({this.guideName,this.guideInfo, this.isFav });//this.next, this.previous});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Color.fromRGBO(220, 20, 60, 1.0),
title: Text('Introduction'),
centerTitle: true,
elevation: 10.0,
actions: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(0.0,2.0,50.0,0.0),
child: MyStatefulWidget(isFav: isFav,name: guideName,),
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Back'),
textColor: Colors.white,
color: Color.fromRGBO(220, 20, 60, 0.8),
),
),
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlWidget(
guideInfo,
)
),
),
),
],
));
}
}
class MyStatefulWidget extends StatefulWidget {
bool isFav;
String name;
MyStatefulWidget({Key key, this.isFav, this.name}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: widget.isFav ? Icon(Icons.bookmark, color: Colors.black) : Icon(Icons.bookmark_border),
onPressed: () {
setState(() {
widget.isFav = !widget.isFav;
if(widget.isFav){
bookmarked.add(widget.name);
bookmarked = bookmarked;
}else{
bookmarked.remove(widget.name);
bookmarked = bookmarked;
}
});
},
),
],
);
}
}
As mentioned, the guidestyle.dart updates the list while the app is running but the list is reset when the app is restarted.
I was looking into using sqflite but it seems overkill so I am unsure about my other options. Any help would be appreciated!

You can use the SharedPreferences package or any other method that is able to persist data between app launches. See this for options to persist data.
Options:
Persist data with SQLite (Though you don't want to use it, it is still an option)
Read and write files
Store key-value data on disk(SharedPreferences) - This is the simplest and will probably suit your needs just fine
If you are using SharedPreferences the setStringList method will suit your needs perfectly.
As a side note, the line bookmarked = bookmarked; is useless.

List<String> bookmarked = []; this always init your data empty
First, You need store package shared_preferences or sqflite or etc..
You can find here => https://pub.dev/
and then check data exist.
After, if(hasData) bookmarked = "loaded data" else bookmarked = [];

Related

What can I do to make my ListView stop incrementing the data every time I open it?

My first Flutter project, which is a tricycle booking system, has just begun. Using the ListView widget, I wanted to display all of the active passengers that are saved in my Firebase Database. However, when I attempted to display it and place it in a List, all functions are working fine at first click. When you click the button to view the ListView a second time, all of the saved data are replicated. The list continues after my third click and grows by three. The image below illustrates what takes place when I repeatedly click on the ListView.
These are the blocks of code that are utilized for this functionality:
CODE for Functionality
retrieveOnlinePassengersInformation(List onlineNearestPassengersList) async
{
dList.clear();
DatabaseReference ref = FirebaseDatabase.instance.ref().child("passengers");
for(int i = 0; i<onlineNearestPassengersList.length; i++)
{
await ref.child(onlineNearestPassengersList[i].passengerId.toString())
.once()
.then((dataSnapshot)
{
var passengerKeyInfo = dataSnapshot.snapshot.value;
dList.add(passengerKeyInfo);
print("passengerKey Info: " + dList.toString());
});
}
}
CODE for the UI
body: ListView.builder(
itemCount: dList.length,
itemBuilder: (BuildContext context, int index)
{
return GestureDetector(
onTap: ()
{
setState(() {
chosenPassengerId = dList[index]["id"].toString();
});
Navigator.pop(context, "passengerChoosed");
},
child: Card(
color: Colors.grey,
elevation: 3,
shadowColor: Colors.green,
margin: EdgeInsets.all(8.0),
child: ListTile(
leading: Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Icon(
Icons.account_circle_outlined,
size: 26.sp,
color: Color(0xFF777777),
),
),
title: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Text(
dList[index]["first_name"] + " " + dList[index]["last_name"],
style: TextStyle(
fontFamily: "Montserrat",
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
Icon(
Icons.verified_rounded,
color: Color(0xFF0CBC8B),
size: 22.sp,
),
],
),
],
),
),
),
);
},
),
Expected Result:
Actual Result AFTER CLICKING MANY TIMES:
Made a demo for you how to call function once on load
class CustomWidgetName extends StatefulWidget {
const CustomWidgetName({Key? key}) : super(key: key);
#override
State<CustomWidgetName> createState() => _CustomWidgetNameState();
}
class _CustomWidgetNameState extends State<CustomWidgetName> {
List? dList = [];
void myDataFunction() async {
// do your data fetch and add to dList
final newList = [];
setState(() {
dList = newList;
});
}
#override
void initState() {
super.initState();
myDataFunction(); // Call your async function here
}
#override
Widget build(BuildContext context) {
return Scaffold();
}
}
Try this solution.
Update SelectNearestActiveDriversScreen() like this:
class SelectNearestActiveDriversScreen extends StatefulWidget
{
DatabaseReference? referenceRideRequest;
final List list;
SelectNearestActiveDriversScreen({this.referenceRideRequest, required this.list});
#override
State<SelectNearestActiveDriversScreen> createState() => _SelectNearestActiveDriversScreenState();
}
In homepage.dart, declare List dList = [];, then change line 378 like this:
Navigator.push(context, MaterialPageRoute(builder: (c)=> SelectNearestActiveDriversScreen(list: dList)));
In SelectNearestActiveDriversScreen(), replace all dList with widget.list.
Finally, if you are using variables in a specific file declare them in that file(not in another file) or pass them in the constructor of the class / file / widget /screen you are calling.
If you would rather use global variables and state managers go for packages like GetX.

Stack with global z-index?

I have a DataTable in which some cells have links. Ideally, I would like to fetch a preview about the link's content whenever hovering over the link, which I was able to achieve using the Stack widget. However, since the stacked preview is inside the DataCell, it seems like I'm not able to raise its "z-index" to be on top of the rest of the table.
Is this not possible with Flutter, or is there a way around it?
The only way I imagine this working, without something to update a global z-index, would be for the cell to update a global state and then have the thumbnail preview appear on a Stack above the DataTable level. But I wish there was a less clunkier way to do it...
3 widgets I've tried but to no avail — they might work, I don't know —:
Tooltip
Overlay
FloatingActionButton
My whole app is here, and the precise commit is 0303732. The relevant code is this ClickableLink widget:
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
import '../schema/links.dart';
#immutable
class ClickableLink extends StatefulWidget {
const ClickableLink({
Key? key,
required this.link,
this.linkText,
this.color = Colors.blue,
}) : super(key: key);
final Link link;
final String? linkText;
final Color color;
#override
State<ClickableLink> createState() => _ClickableLinkState();
}
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
setState(() {
if (widget.link.host == 'online-go.com' && widget.link.prePath == 'game') {
hoverWidget = Positioned(
top: 25,
child: Image.network('https://online-go.com/api/v1/games/${widget.link.id}/png'),
);
}
});
}
void _onExit(PointerEvent pointerEvent) {
setState(() {
hoverWidget = const SizedBox.shrink();
});
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
onExit: _onExit,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(color: widget.color),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
hoverWidget,
],
),
);
}
}
The problem here is due to the fact that your Stack widget, defined inside ClickableLink, will be at a "lower" point (inside your app widget tree) than every other GameResultCell.
So even the higher z-index will still be behind the other GameResultCells.
To fix this I would reccomend changing your structure and define an higher point in your structure to show the preview.
Another way could be using a library to nest your preview inside a tooltip. Take a look at this one for example:
just_the_tooltip: ^0.0.11+2. With this package, you could even use a StatelessWidget.
The result here is more similar to what I suppose you were expecting.
class ClickableLink extends StatelessWidget {
#override
Widget build(BuildContext context) {
return JustTheTooltip(
content: Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png',
),
child: SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
);
}
}
Lastly you could use a Dialog, but the resulting behaviour is a bit different.
Take a look at this code if you want to try:
class _ClickableLinkState extends State<ClickableLink> {
Widget hoverWidget = const SizedBox.shrink();
void _fetchPreview(PointerEvent pointerEvent) {
showDialog(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(
'https://online-go.com/api/v1/games/${widget.link.id}/png'),
const SizedBox(
height: 16.0,
),
TextButton(
onPressed: () async => launch(widget.link.completeLink),
child: const Text('Go to complete link'))
],
),
);
},
);
}
#override
Widget build(BuildContext context) {
return MouseRegion(
onHover: _fetchPreview,
child: Stack(
clipBehavior: Clip.none,
children: [
SelectableText.rich(
TextSpan(
text: widget.linkText ?? widget.link.id,
style: TextStyle(
color: widget.color ??
(DogempTheme.currentThemeIsLight(context)
? const Color(0xff1158c7)
: Colors.orange.withOpacity(0.85)),
),
recognizer: TapGestureRecognizer()
..onTap = () async => launch(widget.link.completeLink),
),
),
],
),
);
}
}

How to update a widget state from another widget in Flutter using global Key?

I have a main widget screen contain two main widgets a Header (marked with red) and a list (marked with purple)
here is my code for that :
class ScreenClient extends StatefulWidget {
_ClientState createState() => _ClientState();
}
class _ClientState extends State<ScreenClient> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ClientHeader(), // this is my header widget red
Expanded(
child: ClientList(), // this is my list widget purple
),
],
);
}
}
the header widget has three options as you can see Tous Bloqué and ayant Retard , what I'm trying to achieve is pass the value of the clicked option to the list widget marked with purple (because those options are filters and the list elements should be shown based on the chosen option)
I have a hard time understanding state management packages and from what I understand Global Keys can do the trick but How ? .
here is my header widget code :
class ClientHeader extends StatefulWidget {
_HeaderClientState createState() => _HeaderClientState();
}
class _HeaderClientState extends State<ClientHeader> {
String nomSituation;
String option = "Tous";
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
GestureDetector(
child: Text(
"Tous",
style: TextStyle(
color: option == "Tous" ? Colors.white : Colors.grey[400],
),
),
onTap: () {
setState(() {
option = "Tous";
});
},
),
GestureDetector(
child: Text(
"Bloqué",
style: TextStyle(
color: option == "Bloqué" ? Colors.white : Colors.grey[400],
),
),
onTap: () {
setState(() {
option = "Bloqué";
//add send value to ClientList widet ?
});
},
),
GestureDetector(
child: Text(
"Ayant Retard",
style: TextStyle(
color:
option == "Ayant Retard" ? Colors.white : Colors.grey[400],
),
),
onTap: () {
setState(() {
option = "Ayant Retard";
});
},
),
],
),
);
}
}
I suggest you can watch 2 examples in this video Pragmatic State Management in Flutter (Google I/O'19)about state mangement. This video helped me a lot when I learn flutter in the begining. They explain how to control the StatefulWidget from the other one:
Make state global, controlled by another widget (from 5m30s)
Use Provider, which is a very popular solution in Flutter, to control share the value between 2 widgets (from 15m05s)
You you have more time, you can study more fancy state management method like Bloc, MobX (List of state management approaches) or even the advance version of Provider named riverpod just pushish few months ago, which try to resolve some cons when using Provider.

My flutter app, generate multiple register in DB when I press button once

I have an app that contains a form. First, you have to authenticate with your ID, a function checks the date of your last register (if you don't register today, you'll pass. If not you can not log into the form screen). Second, you register your symptoms once a day(restriction of the app), press "ENVIAR(send)" and a POST method storages your data in a DB. It's simple.
In theory, I should see one register per person per day. Now the number of devices with my app installed has increased I can see multiple registers per person in some cases. Curiously the problem happens in just some devices. I cannot detect the problem because with my phone I've never had these kinds of problems (HUAWEI P30 LITE).
I try to debug my code, but it all works perfectly. Could you help or advise me on how to solve this problem, please?
PDT: when I press the button "SEND", the function _submit() is executed, which allows POST the data in the DB. Additionally, to verify the systems works right, I use a developer account with credential "000000000". I had to eliminate many lines, but these are the most important.
Have a nice day.
Thanks
I attach the code:
class Tamizaje1Page extends StatefulWidget {
#override
_Tamizaje1PageState createState() => _Tamizaje1PageState();
}
class _Tamizaje1PageState extends State<Tamizaje1Page> {
final usuariosProvider = new UsuariosProvider();
final appProvider = new AppProvider();
final formKey = GlobalKey<FormState>();
final productoProvider = new ProductosProvider();
ProductoModel producto = new ProductoModel();
AppModel app = new AppModel();
#override
Widget build(BuildContext context) {
final bloc = Provider.of(context);
mostrarPosision();
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Center(child: Text('Cuestionario Diario')),
),
body: WillPopScope(
onWillPop: (){Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => LoginPage()), (route) => false);},
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(15.0),
child: Form(
key: formKey,
child: Column(
children: <Widget>[
_crearBoton1(context),
],
),
),
),
),
),
);
}
Widget _crearBoton1( BuildContext context) {
final size = MediaQuery.of(context).size;
return ButtonTheme(
minWidth: size.width*0.2,
height: size.height*0.07,
focusColor: colorApp,
child: RaisedButton(
child: Container(padding: EdgeInsets.symmetric( horizontal: 80.0, vertical: 15.0),child: Text('Enviar', style: TextStyle(color: Colors.white,fontSize: 16.0),)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25.0),side: BorderSide(color: Colors.transparent,width: 2.0)),
elevation: 0.0,
color: colorApp,
textColor: Colors.white,
onPressed: ( _guardando ) ? null : _submit,
),
);
}
//THIS FUNCTION SENDS THE DATA WHEN PRESS THE BUTTON
_submit() async {
_noVisibleData();
if ( !formKey.currentState.validate() ) return;
formKey.currentState.save();
if(producto.nroDoc!="000000000" && _currentPosition != null && ((producto.meSiento=='1' && app.diagnostico!='-1') || (producto.meSiento=='0'))){
bool permtirenvioToDB= await productoProvider.crearProducto(producto); //envia los datos
if(permtirenvioToDB==true){
_alertaDeEnvio(context, envio1);
await enviarGMAIL();
appProvider.crearApp(app);
Navigator.push(context, MaterialPageRoute(builder: (context) => AlertPage()));
}else{
contarIntetentosEnvioDB++;
if(contarIntetentosEnvioDB<5)_soloMensaje(context,"Se ha producido un error al enviar el formulario. Por favor... ¡Inténtalo nuevamente!","assets/alerta0/a0i1.svg",80.0);
else _soloMensaje(context,"Por favor, ¡Comuníquese con el área de Tecnologías de la Información!","assets/alerta0/a0i3.svg",80.0);
}
}else{
_alertaDeEnvio(context, envio1);
print('Prueba de desarrollador');
Navigator.push(context, MaterialPageRoute(builder: (context) => AlertPage()));
}
}
}
Thanks, i debugged many option and realised the errors occurs because i have 3 future functions wich execute when i press the button (inside the butto widget). I created i new page with an additional #averride before Widget build exclusive to execute future functions, and it works good.
class _MailDBSendState extends State<MailDBSend> {
#override
void initState() {
super.initState();
_dataBase();
_sendMail();
}
.
.
.
.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(

In Dart/Flutter, how do I use a variable from a method so I can ouput it to a text field

Hope somebody can help - I hit this dead end a few weeks ago and think that I've tried everything within my limited knowledge.
I've set up a database that works OK - that is I can add data on one screen, review the data and edit the data on another screen. Now I want to sum one of the columns (beef) which I've been able to do as proven in the 'debugPrint' to the console. I want to access this variable 'beefTotal' from the 'sumBeef' method and print show this in a text field in the UI. I just can't manage it though. It just returns null.
Thanks in advance for any help.
import 'package:flutter/material.dart';
import 'package:take_note/utils/database_helper.dart';
class Info extends StatefulWidget {
#override
State<StatefulWidget> createState() => _InfoState();
}
DatabaseHelper helper = DatabaseHelper();
var database = DatabaseHelper();
class _InfoState extends State<Info> {
List beefTotal;
#override
Widget build (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Beef Info"),
backgroundColor: Colors.lightBlueAccent,
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: RaisedButton(
onPressed: (){
sumBeef();
},
),
),
),
Expanded(
child: Center(
child: Text("Total Beef is: £ $beefTotal", style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 30.0,
fontWeight: FontWeight.w400
),),
),
),
],
),
)
);
}
void sumBeef () async {
beefTotal = await database.addBeef();
debugPrint("Total beef: $beefTotal");
}
}
The code below is from a class called DatabaseHelper which the method sumBeef() uses
Future<List<Map<String, dynamic>>> addBeef()async{
Database db = await this.database;
var result = await db.rawQuery("SELECT SUM(beef) FROM $table");
return result;
}
```[enter image description here][1]
[1]: https://i.stack.imgur.com/L46Gj.png
Just call
setState({
});
void sumBeef () async {
beefTotal = await database.addBeef();
setState(() {});
debugPrint("Total beef: $beefTotal");
}
and your good! anytime you make a change you have to call setState method to update the ui (rebuild) in flutters case