Flutter - Pass data from a Future variable(sqlite) to a widget - android

I am very new to Flutter and Dart but I am trying to get data from a sqlite method and pass it as an argument to a new widget.
I've tried using FutureBuilder and it didn't help.
I am calling the method to load the data, wait for the data in .then() and inside then I set the state of a class variable to later be assigned to widget but the variable _records is always null even after setState.
Below is my code
int initScreen;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
initScreen = await prefs.getInt("initScreen");
await prefs.setInt("initScreen", 1);
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State{
List<Quarter> _records;
#override
void initState() {
_getRecords();
super.initState();
}
_getRecords() async {
await QuarterData().getQuarters().then((list){
setState((){
this. _records = list;
});
});
return list;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'UI',
debugShowCheckedModeBanner: false,
home: initScreen == 0 || initScreen == null
? SpashScreen(this._records)
: QuartersHome(this._records)
);
}
}
Many thanks

this._records is not initialised and will be null until your query is not done.
You need to check if it's not null before returning your widgets with it (like you do with initScreen)
If after the setState()method your variable is still null, maybe it's your query which return null.

Related

Flutter: local_auth can be bypassed by pressing the back button

I'm using local_auth for user verification.
The root widget of my app is a stateless widget.
The bug:
The authentication screen pops up as usual. If the fingerprint (or pin) matches, the user can then access the app. However, if the back button is pressed (while the authentication screen is still up), the authentication window vanishes and the user can access the app without authenticating.
I'm using Flutter-2.5.3 and local_auth-1.1.8.
This is the main.dart:
//other imports here
import 'package:local_auth/local_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var localAuth = LocalAuthentication();
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool('auth') == true) {
await localAuth.authenticate(
localizedReason: 'Authenticate to access Notes',
useErrorDialogs: true,
stickyAuth: true,);
}
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
runApp(ProviderScope(child: MyApp()));
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
//returns my widget
}
}
I tried moving the runApp block under a conditional, such that the main root window gets called only when the authentication was successful. However the result remained the same.
This was what I did:
if (prefs.getBool('auth') == true) {
var authenticate = await localAuth.authenticate(
localizedReason: 'Authenticate to access Notes',
useErrorDialogs: true,
stickyAuth: true,);
if (authenticate == true) {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
runApp(ProviderScope(child: MyApp()));
});
}
}
This is what worked for me:
Changing MyApp to a StatefulWidget.
Adding an awaited function that attempts to authenticate the user before the user can access the widget (that is, the build function).
Modifying the code:
//other imports here
import 'package:local_auth/local_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
//removing the authentication block from the main method
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
runApp(ProviderScope(child: MyApp()));
});
}
class MyApp extends StatefulWidget { //changing MyApp to StatefulWidget
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
_authenticate(); //the function that handles authentication
}
void _authenticate() async {
var localAuth = LocalAuthentication();
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool('auth') == true) {
var authenticate = await localAuth.authenticate(
localizedReason: 'Authenticate to access Notes',
useErrorDialogs: true,
//Not using stickyAuth because: https://github.com/flutter/flutter/issues/83773
// stickyAuth: true,
);
if (authenticate != true)
exit(0); //exiting the app if the authentication failed
}
}
#override
Widget build(BuildContext context) {
//returns my widget
}
}

Error: Could not find the correct Provider<User> above this Wrapper Widget

I am following youtube tutorial for building an app using flutter. For some reason, I am getting this error. I could not find any fix for this issue.
//The following ProviderNotFoundError was thrown building Wrapper(dirty):
Error: Could not find the correct Provider above this Wrapper Widget
To fix, please:
Ensure the Provider is an ancestor to this Wrapper Widget
Provide types to Provider
Provide types to Consumer
Provide types to Provider.of()
Always use package imports. Ex: `import 'package:my_app/my_code.dart';
Ensure the correct context is being used.
Here is my code for main.dart
void main() => runApp(Gradebook());
class Gradebook extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(
home: Wrapper(),
),
);
}
Wrapper.dart
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null){
return Authenticate();
} else{
return Home();
}
}
}
Here is where the stream provider is created.
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create user obj based on firebase user
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
//auth change user stream
Stream<User> get user {
return _auth.onAuthStateChanged
.map((FirebaseUser user) => _userFromFirebaseUser(user));
}
}
Here is Authenticate.dart
class Authenticate extends StatefulWidget {
#override
_AuthenticateState createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
bool showLogin = true;
void toggleView() {
setState(() => showLogin = !showLogin);
}
#override
Widget build(BuildContext context) {
if(showLogin){
return Login(toggleView: toggleView);
} else{
return Register(toggleView: toggleView);
}
}
}

Flutter: Why Future.then() doesn't work on class variable?

Code I show you is the simplified code which I'm troubled in.
My expected result is [1,2,3,4,5,6], but app says [1,2,3].
I know "loadMoreInterger()" should be in "initState()", but for some reason I have to put it in Widget build() {"HERE"}.
I wonder if why doesn't it work, and the solution for correct result.....
I really appreciate for your help :)
import 'package:flutter/material.dart';
import 'dart:async';
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();
}
// ↓↓↓↓↓↓↓↓↓↓↓WHERE I CANNOT UNDERSTAND↓↓↓↓↓↓↓↓↓↓↓
class _MyHomePageState extends State<MyHomePage> {
List<int> intList = [1,2,3];
Future<List<int>> loadMoreInteger() async {
print('Future');
return [4,5,6];
}
#override
Widget build(BuildContext context) {
loadMoreInteger().then((value) {
intList.addAll(value); // why doesn't it work?
});
print("console: $intList");
return Scaffold(
body: Center(
child: Text("display: $intList")
)
);
}
}
//Expected result: [1,2,3,4,5,6]
//Actual result: [1,2,3]
put it in initState override function and it works for yu !!!!
List<int> intList = new List();
Future<List<int>> loadMoreInteger() async {
print('Future');
return [4,5,6];
}
#override
void initState() {
super.initState();
intList = [1,2,3];
loadMoreInteger().then((v){
setState(() {
intList.addAll(v) ;
});
}); }
Here is what your build method does: after entering the method it starts to execute loadMoreInteger() future. Afterwards even if executed future is synchronous it only schedules call of next future that is produced by calling .then. So build method continues to execute with old intList value. And [4,5,6] will be added only after build completes.
In general you can wait for future to complete by calling it with await keyword. But build method is overriden and already has predefined return type that is not future, so you can not call await inside build.
What you can do:
I highly recommend moving any manipulation with data from build method. Its purpose is to produce widgets as fast as possible. It can be called multiple times at some moment unexpected for developer.
One of possible options for you will be moving loadMoreInteger() to initState and calling setState when intList is updated
#override
void initState() {
super.initState();
loadMoreInteger().then((value) {
setState(() {
intList.addAll(value);
});
});
}

Android's onStart equivalent

I am building an app which has local database and I keep some infos about movies in that database. There is a screen called "Favorites" where I get the movies from local database and put them in a listview:
And if I click any movie from that list, I will be directed to a new Screen where I can see movie details. In this screen, there is a button which removes the movie from local database.
Now my problem starts here: After I remove any movie from database and go back to "Favorites" Screen, I still see that movie in ListView. But I have already removed that movie from database. If I was doin this on Android platform, I would override onStart method and make a query to database, then update the listview. But in Flutter as long as I know, there is no method as onStart. So, what should I do?
class FavoritesScreen extends StatefulWidget {
#override
_FavoritesScreenState createState() => _FavoritesScreenState();
}
class _FavoritesScreenState extends State<FavoritesScreen> {
var favoriteMovieDatabase = FavoriteMovieDatabase();
List<MovieOverview> movieOverviewList = List<MovieOverview>();
#override
void initState() {
super.initState();
favoriteMovieDatabase
.getAllFavoriteMovies()
.then((List<MovieOverview> list) {
setState(() {
movieOverviewList = list;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Favorites")),
body: MovieOverviewListView(movieOverviewList, "FAVORITE")
);
}
}
EDIT:
I have tried to use WidgetsBindingObserver but it did not help.
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if(state == AppLifecycleState.resumed){
favoriteMovieDatabase
.getAllFavoriteMovies()
.then((List<MovieOverview> list) {
setState(() {
movieOverviewList = list;
});
});
}
}
It did not even call this method.
As a simple solution, you can await the result of a navigator push:
goToDetailPage(BuildContext context) async {
await Navigator.of(context).pushNamed('movieDetail');
// this is reached when the route is popped
reloadData();
}
To make the code even more efficient, you can return a result from your detail route that signifies if the movie was deleted:
Navigator.pop(context, 'MOVIE_DELETED');
and
goToDetailPage(BuildContext context) async {
final result = await Navigator.of(context).pushNamed<String>('movieDetail');
if(result == 'MOVIE_DELETED') {
reloadData();
}
}
In the long run, when there are more ways to change the database content (think of a semitransparent dialog where the movie list is still visible in background, or live updates), it would be better to move to some kind of reactive architecture similar to Room on Android.
Assuming your problem happens when you use Navigator to push a new route, then you can use RouteAware mixin combined with RouteObserver and then overrides didPopNext method.
didPopNext is called whenever a route becomes visible again.
final RouteObserver<PageRoute> routeObserver = new RouteObserver<PageRoute>();
void main() {
runApp(new MaterialApp(
home: new Container(),
navigatorObservers: [routeObserver],
));
}
class RouteAwareWidget extends StatefulWidget {
State<RouteAwareWidget> createState() => new RouteAwareWidgetState();
}
// Implement RouteAware in a widget's state and subscribe it to the RouteObserver.
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPopNext() {
// TODO: query DB again
}
#override
Widget build(BuildContext context) => new Container();
}
You can achieve this with the initState:
#override
void initState() {
_doWhatYouWantToDo().then((value){
print('Async done');
});
super.initState();
}
add these code when you want to run method on pop
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen() ,
)).then((value) {
//add your method which you want to run on pop!! cheers
setState(() {});
});
},

Flutter - setState not updating inner Stateful Widget

Basically I am trying to make an app whose content will be updated with an async function that takes information from a website, but when I do try to set the new state, it doesn't reload the new content. If I debug the app, it shows that the current content is the new one, but after "rebuilding" the whole widget, it doesn't show the new info.
Edit: loadData ( ) method, basically read a URL with http package, the URL contains a JSON file whose content changes every 5 minutes with new news. For example a .json file with sports real-time scoreboards whose scores are always changing, so the content should always change with new results.
class mainWidget extends StatefulWidget
{
State<StatefulWidget> createState() => new mainWidgetState();
}
class mainWidgetState extends State<mainWidget>
{
List<Widget> _data;
Timer timer;
Widget build(BuildContext context) {
return new ListView(
children: _data);
}
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
String s = await loadData();
this.setState(() {
_data = <Widget> [new childWidget(s)];
});
});
}
}
class childWidget extends StatefulWidget {
childWidget(String s){
_title = s;
}
Widget _title;
createState() => new childState();
}
class childState extends State<gameCardS> {
Widget _title;
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: foo(),
child: new Card(child: new Text(_title));
}
initState()
{
super.initState();
_title = widget._title;
}
}
This should sort your problem out. Basically you always want your Widgets created in your build method hierarchy.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(home: new Scaffold(body: new MainWidget())));
class MainWidget extends StatefulWidget {
#override
State createState() => new MainWidgetState();
}
class MainWidgetState extends State<MainWidget> {
List<ItemData> _data = new List();
Timer timer;
Widget build(BuildContext context) {
return new ListView(children: _data.map((item) => new ChildWidget(item)).toList());
}
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
ItemData data = await loadData();
this.setState(() {
_data = <ItemData>[data];
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
static int testCount = 0;
Future<ItemData> loadData() async {
testCount++;
return new ItemData("Testing #$testCount");
}
}
class ChildWidget extends StatefulWidget {
ItemData _data;
ChildWidget(ItemData data) {
_data = data;
}
#override
State<ChildWidget> createState() => new ChildState();
}
class ChildState extends State<ChildWidget> {
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: () => foo(),
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
child: new Card(
child: new Container(
padding: const EdgeInsets.all(8.0),
child: new Text(widget._data.title),
),
),
)
);
}
foo() {
print("Card Tapped: " + widget._data.toString());
}
}
class ItemData {
final String title;
ItemData(this.title);
#override
String toString() {
return 'ItemData{title: $title}';
}
}
This was really giving me headache and no Google results were working. What finally worked was so simple. In your child build() assign the value to the local variable before you return. Once I did this everything worked with subsequent data loads. I even took out the initState() code.
Many thanks to #Simon. Your answer somehow inspired me to try this.
In your childState:
#override
Widget build(BuildContext context) {
_title = widget._title; // <<< ADDING THIS HERE IS THE FIX
return new GestureDetector(onTap: foo(),
child: new Card(child: new Text(_title));
}
Hopefully this works in your code. For me, I use a Map for the entire JSON record passed in, rather than a single String, but that should still work.
The Root issue explained
initState(), for the child widget, is called only once when the Widget is inserted into the tree. Because of this, your child Widget variables will never be updated when they change on the parent widget. Technically the variables for the widgets are changing, you are just not capturing that change in your state class.
build() is the method that gets called every time something in the Widget changes. This is the reason #gregthegeek solution works. Updating the variables inside the build method of your child widget will ensure they get the latest from parent.
Works
class ChildState extends State<ChildWidget> {
late String _title;
#override
Widget build(BuildContext context) {
_title = widget._title; // <==== IMPORTANT LINE
return new GestureDetector(onTap: () => foo(),
child: new Text(_title),
);
}
}
Does not work
(It will not update when _title changes in parent)
class ChildState extends State<ChildWidget> {
late String _title;
#override
void initState() {
super.initState();
_title = widget._title; // <==== IMPORTANT LINE
}
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: () => foo(),
child: new Text(_title),
);
}
}
I'm unsure why this happens when calling setState(...) in an async function, but one simple solution is to use:
WidgetsBinding.instance.addPostFrameCallback((_) => setState(...));
instead of just setState(...)
This fixed my issue... If you have an initial value to be assigned on a variable use it in initState()
Note : Faced this issue when I tried to set initial value inside build function.
#override
void initState() {
count = widget.initialValue.length; // Initial value
super.initState();
}
don't use a future within a future; use different function that will return each future individually like this
List<Requests> requestsData;
List<DocumentSnapshot> requestsDocumentData;
var docId;
#override
void initState() {
super.initState();
getRequestDocs();
}
Future<FirebaseUser> getData() {
var _auth = FirebaseAuth.instance;
return _auth.currentUser();
}
getRequestDocs() {
getData().then((FirebaseUser user) {
this.setState(() {
docId = user.uid;
});
});
FireDb()
.getDocuments("vendorsrequests")
.then((List<DocumentSnapshot> documentSnapshots) {
this.setState(() {
requestsDocumentData = documentSnapshots;
});
});
for (DocumentSnapshot request in requestsDocumentData) {
this.setState(() {
requestsData.add(Requests(
request.documentID,
request.data['requests'],
Icons.data_usage,
request.data['requests'][0],
"location",
"payMessage",
"budget",
"tokensRequired",
"date"));
});
}
}
you can create individual functions for
FireDb().getDocuments("vendorsrequests")
.then((List<DocumentSnapshot> documentSnapshots) {
this.setState(() {
requestsDocumentData = documentSnapshots;
});
});
and
for (DocumentSnapshot request in requestsDocumentData) {
this.setState(() {
requestsData.add(Requests(
request.documentID,
request.data['requests'],
Icons.data_usage,
request.data['requests'][0],
"location",
"payMessage",
"budget",
"tokensRequired",
"date"));
});
}
I found that the use of
this
with setState is must
The real issue on child StatefulWidget not rebuilding is in the KEY
Hey, I'm a bit late to the discussion, but I think this is important.
I was facing a similar problem a while back and I even came to this thread to get some ideas.
In my case, I was simply getting widget.value directly inside the build method of the childWidget, and it was not updating when i called setState in the mainWidget.
Then i found this video: https://youtu.be/kn0EOS-ZiIc
(When to Use Keys - Flutter Widgets 101 Ep. 4) -
Here the Google dev talks about how keys in Flutter.
The short answer is
In a StatefulWidget the actual value you pass is stored in the state, not in the widget itself, like a StatelessWidget does.
When you call setState in the mainWidget, Flutter walks down the widget tree and checks each childWidget's type and key, to see if anything has changed. As stateful widgets store their values in the state, Flutter thinks the child widgets did not change (because the types and keys are the same) and does not rebuild them, even if the value changed.
The real solution is to give the widget a key containing the value that is changing, so when Flutter is walking down the tree, it notices that the key changed, and rebuilds the stateful widget.
Other solutions here may work as well, but if you want to really understand it, this video is worth watching.
first check whether it is a stateless or stateful widget,and if the class is stateless then make it to a stateful widget and try adding a code after closing the
setState(() { _myState = newValue; });
In my case, it was just defining the state as a class property and not a local variable in the build method
Doing this -
List<Task> tasks = [
Task('Buy milk'),
Task('Buy eggs'),
Task('Buy bread'),
];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) => TaskTile(
...
instead of this -
#override
Widget build(BuildContext context) {
List<Task> tasks = [
Task('Buy milk'),
Task('Buy eggs'),
Task('Buy bread'),
];
return ListView.builder(
itemBuilder: (context, index) => TaskTile(
...
Found the best solution.
If you are using a stateless widget you can't use set state, so just convert the stateless widget to statefull widget

Categories

Resources