Flutter + SharedPreferences: a build function returned null - flutter

I'm trying to get access to the SharedPreferences and create a permanent file named "first_run".
At the first start of the application it should return "true" and then change it to false.
I declared a future function that return true or false based on that.
Now i got a Wrapper() widget that shows either Loading... , the HomeScreen() or the LoginScreen()
based on the result of the future function.
Why is it that the build function returns null ?
How can I avoid the "first_run" to get deleted when I update the app ?
Here's the code:
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../screens/home_screen.dart';
import '../screens/login_screen.dart';
import '../providers/auth_provider.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
FirebaseAuth auth = FirebaseAuth.instance;
#override
void initState() {
AuthProvider().isUserLoggedIn();
super.initState();
}
#override
Widget build(BuildContext context) {
FutureBuilder(
future: hasAlreadyStarted(),
builder: (context, snapshot) {
if (snapshot.hasData == true) {
return LoginScreen();
} else {
return CircularProgressIndicator(
backgroundColor: Colors.deepOrange,
);
}
},
);
}
Future <bool> hasAlreadyStarted() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool("first_run") == true) {
prefs.setBool("first_run", false);
return true;
} else {
return false;
}
}
}

You need return keyword
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: hasAlreadyStarted(),
builder: (context, snapshot) {
if (snapshot.hasData == true) {
return LoginScreen();
} else {
return CircularProgressIndicator(
backgroundColor: Colors.deepOrange,
);
}
},
);
}

Apart from the fact that you need a return, (which the other answer already pointed out), you should also not produce a new Future every time the build method is called.
Future<bool> _yourFuture;
#override
void initState() {
AuthProvider().isUserLoggedIn();
super.initState();
_yourFuture = hasAlreadyStarted(); // <= start it once
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _yourFuture, // <= reference it for every build

Related

How To Work with Flutter Data Model and Future Builder

i have working with test app, its just display list of employees from api call, for that i have created data model for employee and calling it. but i get nothing i know somewhere it goes wrong help me to find out the problem(actually no errors but, its does not load the data).
here is the snippets
import 'package:flutter/material.dart';
import '../models/employee.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class EmployeeListScreen extends StatefulWidget {
EmployeeListScreen({Key key}) : super(key: key);
#override
_EmployeeListScreenState createState() => _EmployeeListScreenState();
}
class _EmployeeListScreenState extends State<EmployeeListScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Employee List"),
),
body: FutureBuilder(
future: fetchEmployees(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
return Center(
child: Text("None"),
);
}
if (snapshot.connectionState == ConnectionState.active) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return Center(child: Text("No Employees"));
} else {
return Center(
child: ListView.builder(
itemCount: snapshot.data.length[![enter image description here][1]][1],
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data[index]["name"]);
},
),
);
}
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container();
},
));
}
Future<List<Employee>> fetchEmployees() async {
final response = await http.get(
"http://192.168.1.199/projects/ci/employee/api/getEmployees",
headers: {"accept": "application/json"});
debugPrint("Api Finished...");
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
Iterable list = result['employees'];
print(list);
return list.map((employee) => Employee.fromJson(employee)).toList();
} else {
throw Exception("Failed to Load Employees");
}
}
}
see the screen shots.
i have the result while am using traditional api call without using model and factory methods, its very confusing to me also suggest me for best sites to learn these things, even i saw the official document it not clear at all.
To help debug the issue, how about trying this simplified code below. Call your fetchEmployees() from inside loadSlowData() method.
(It's not good practice to make an async call directly in FutureBuilder future:. Instead, make the async call in initState of the StatefulWidget. Since FutureBuilder is inside the build() method, and build could be called up to 60 times a second, you can obviously see the problem. If you happen to use an animation on that part of the widget tree, which refresh at 60fps, you'll get that situation.)
import 'package:flutter/material.dart';
class FutureBuilderStatefulPage extends StatefulWidget {
#override
_FutureBuilderStatefulPageState createState() => _FutureBuilderStatefulPageState();
}
class _FutureBuilderStatefulPageState extends State<FutureBuilderStatefulPage> {
Future<String> _slowData;
#override
void initState() {
super.initState();
_slowData = loadSlowData();
}
Future<String> loadSlowData() async {
// replace with your async call ↓ ↓
return await Future.delayed(Duration(seconds: 2), () => 'The Future has arrived');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FutureBldr Stateful'),
),
body: FutureBuilder<String>(
future: _slowData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(child: Text(snapshot.data));
}
return Center(child: Text('Loading...'));
},
),
);
}
}
You can possibly Try snapShot.hasData instead of snapshot.data

Error: 'await' can only be used in 'async' or 'async*' methods

I am trying to add distance from user to the Location object, but this requires using an asynchronous call that I can't figure out where and how to do exactly. From there I will sort Locations by distance from user. I tried the code below bc it's where the sorted locations would be used, but I get an error saying "await" can only be used in "async" or "async*" methods even though it is being used with an async function. How do I add distance from user to a Location object given it requires an asynchronous call?
class MapWidget extends StatefulWidget {
...
#override
_MapWidgetState createState() => _MapWidgetState();
}
class _MapWidgetState extends
State<MapWidget> {
Future <List<Location>> sortLocations() async {
return null;//function not done
}
#override
Widget build(BuildContext context) {
final List<Location> sortedLocations = await sortLocations();
...
You cannot use await functions in build method because it cannot be async.To use async operations in build you must use FutureBuilder or StreamBuilder.
Future<List<Location>> sortLocations() {
...
return <Location>[];
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Location>>(
future: sortLocations(),
builder: (context, snapshot) {
if(snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()));
}
return ListView(...);
},
);
}
Future<List<Location>> sortLocations() {
...
return <Location>[];
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<Location>>(
stream: sortLocations().asStream(),
builder: (context, snapshot) {
if(snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()));
}
return ListView(...);
},
);
}
In Flutter there is a widget call FutureBuilder, that helps you build UI after the data is returned from an async function. You can use it as:
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Location>>(
future: sortLocations(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container(child: Center(child: CircularProgressIndicator()));
var sortedLocations = snapshot.data;
// Build your UI here
return ...
}
);
you cannot use await in build method instead use it in initState
final List<Location> sortedLocations= new List();
#override
void initState(){
super.initState();
getdata();
}
getdata()async{
sortedLocations.clear();
sortedLocations = await sortLocations();
setState((){});
}

Login Flow Navigation using FutureBuilder: Flutter?

import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AppThemeNotifier>(
builder: (BuildContext context, AppThemeNotifier value, Widget child) {
return Provider(
create: "XXXXXXX",
dispose: "XXXXXXX",
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: AppTheme.getThemeFromThemeMode(value.themeMode()),
home: Base()),
);
},
);
}
}
class Base extends StatefulWidget {
#override
_BaseState createState() => _BaseState();
}
class _BaseState extends State<Base> {
#override
Widget build(BuildContext context) {
return FutureBuilder<SessionAuth>(
future: Provider.of<AppThemeNotifier>(context, listen: false).validate,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
SessionAuth session = new SessionAuth();
if (session.userId != null && session.isLoggedIn) {
return FullApp();
} else if (isFirst) {
return OnBoardingScreen();
}
return LoginScreen();
} else {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
},
);
}
}
I was trying to navigate different screens based on session validation is done through the provider package. (Login Flow Management)
Simply, I want to replace the Screens based on session details
Break Down
If userId is not equal to null and isLoggedin is true -> FullApp
else If user isFirst is true -> OnBoardingScreen
else Login Screen
Error
flutter: The method '>=' was called on null.
flutter: Receiver: null
flutter: Tried calling: >=(0.0)
SessionAuth
class SessionAuth {
SessionAuth({this.isLoggedIn, this.userId, this.isFirst});
int userId;
bool isLoggedIn;
bool isFirst;
}
validate
Future<SessionAuth> get validate async {
SharedPreferences prefs = await SharedPreferences.getInstance();
SessionAuth auth = new SessionAuth();
auth.userId = prefs.getInt('userId') ?? null;
auth.isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
auth.isFirst = prefs.getBool("isFirst") ?? null;
return auth;
}

Why this code is not showing CircularProgressIndicator?

This is a simple code which display a different Text based in a random number. I want to show the CircularProgressIndicator when the user push 'next' button and the method 'getRandom' delays 5 secs.
CircularProgressIndicator never is shown...why?
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
Future<String> random;
#override
void initState() {
super.initState();
random = getRandom();
}
Future<String> getRandom() async{
print("getRandom");
Future.delayed(const Duration(seconds: 5));
return "the number is"+Random().nextInt(100).toString();
}
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(title: Text("Random Widget")),
body: Center(child:
FutureBuilder(
future:random,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(children: [
Text(snapshot.data,textScaleFactor: 4),
getNextButton()
]);
} else if (snapshot.hasError) {
return Text("ERROR");
}
return CircularProgressIndicator();
}
)
)),
);
}
Widget getNextButton(){
return RaisedButton(
child: Text("NEXT"),
color: Colors.red,
onPressed: () {
setState(() {
random=getRandom();
});
}
);
}
}
Thanks in advance!!
There are several mistakes in your code.
You are passing initialData. So the snapshot.hasData will be true in the beginning itself.
You didn't initialise the random for the first time.
You have to await your Future.delay
Change getRandom implementation:
Future<String> getRandom() async{
await Future.delayed(const Duration(seconds: 5));
return "the number is"+ Random().nextInt(100).toString();
}
Implement initState to initialise random future:
#override
void initState() {
super.initState();
random = getRandom();
}
Change future builder:
FutureBuilder(
future:random,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
return Column(children: [
Text(snapshot.data,textScaleFactor: 4),
getNextButton()
]);
} else if (snapshot.hasError) {
return Text("ERROR");
}
return CircularProgressIndicator();
}
)
If your not using your initialData field in you FutureBuilder than delete and your code will work. If you for some reason need that value add the following statement to the end:
if(snapshot.data != 'starting') {
return CircularProgressIndicator();
}
See the docs for an example of how to use it

How can I update the value of the parameter with the value that come from API response?

I am trying to update the value of totalPricewith the value that comes from the response from API. I have created a currentTotal methods that contains setState(). Then passed snapshot.data.price.totalAmountvalue to currentTotal in order to update the value of totalPrice.But, it doesnt update the value. Can you help?
double totalPrice = 0;
#override
Widget build(BuildContext context) {
currentTotal(double x) {
setState(() {
totalPrice += x;
});
}
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FutureBuilder<SearchResult>(
future: serviceOne.postCall(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Material(
child: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
ListTile(
title: new Text(totalPrice.toString()),
)
]),
),
]
)
}
currentTotal(snapshot.data.price.totalAmount);
else if (snapshot.hasError) {
return Text("error....${snapshot.error}");
}
There are many things needs to be fixed in your build.
1 - Your widget is StatefulWidget, to use FutureBuilder inside StatefulWidget read this:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Briefly, create Future future; instance field, then assign it inside the initState and use that future for FutureBuilder.
2 - your setState not inside a method, you have probably syntax error there. Create a void method and use setState inside it.
3 - You don't need to check twice like:
if (snapshot.hasData) {
if (snapshot.data != null) {
One of them enough, after the condition check, call your method includes setState, then display it.
Edit:
Here an example template for your solution:
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
Future<int> future;
int price = 0;
#override
void initState() {
future = fetchPrice();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: Text(price.toString()),
);
}
return Center(child: CircularProgressIndicator());
},
),
),
);
}
Future<int> fetchPrice() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/posts/1');
final data = json.decode(response.body);
setState(() {
price = data['userId'];
});
return data['userId'];
}
}