The issue that I am facing is in future builder in flutter.When opening the page first time the data is loaded successfully but when I go to a different page and then return to the same page it throws an error LateInitializationError: Field 'myfuture' has not been initialized.
Hence if you could please help me resolve this issue.
Please find below the code and let me know if any further information is required from my end.
view.dart
late final Future myfuture;
#override
void initState() {
print('init started'); // on opening second time the process gets stuck here with the above error message
if (Provider.of<FilterOptionProvider>(context, listen: false)
.initialList
.isEmpty) {
myfuture = Provider.of<FilterOptionProvider>(context, listen: false)
.readfilters(checkfilters);
}
super.initState();
}
Widget _buildList() {
final notificationData =
Provider.of<FilterOptionProvider>(context, listen: true);
final ndata = notificationData.initialList;
return FutureBuilder(
future: myfuture,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: LinearProgressIndicator());
} else if (snapshot.error != null) {
return const Center(
child: Text('An error occured'),
);
} else {
final notificationData =
Provider.of<FilterOptionProvider>(context, listen: true);
final ndata = notificationData.initialList;
provider.dart
Future<void> readfilters(Map<String, dynamic> queryPam) async {
Map<String, String> headers = {
"Content-Type": "charset=utf-8",
"Content-type": "application/json"
};
Just init the empty future in else,
if (Provider.of<Filter...) {
...
}
else {
myFuture = Future(() {});
}
Related
I am trying to get the string value of a future, and saving state in flutter. user chooses the endTime and it should display on the UI untill it ends. however, I am getting the following error:
type 'String' is not a subtype of type 'Future<String>' in type cast
the method:
final Future<SharedPreferences> _prefs =
SharedPreferences.getInstance();
Future<String> _textLine = '' as Future<String>;
Future<String> fastTrue() async {
final SharedPreferences prefs = await _prefs;
String formattedDate = DateFormat('yyyy-MM-dd,
hh:mma').format(endTime);
final textLine = (prefs.getString('formattedDate') ??
Languages.of(context)!.setYourFastTime) as Future<String>;
setState(() {
_textLine = prefs.setString('formattedDate',
Languages.of(context)!.endTimeIs
+'\n$formattedDate').then((bool success) {
return textLine;
});
});
return textLine;
}
in initState():
#override
void initState() {
super.initState();
_textLine = _prefs.then((SharedPreferences prefs) {
return prefs.getString('formattedDate') ??
Languages.of(context)!.setEndTime +'\n'+DateFormat('yyyy-MM-dd,
hh:mma').format(DateTime.now());
});
then in my widget build():
Padding(padding: const EdgeInsets.only(top: 170),
child: FutureBuilder<String>(
future: _textLine,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text(
Languages.of(context)!.endTimeIs +
"\n${snapshot.data}"
);
}
}
})),
help me, pls, tried using hive, but was not able to get to save the state of the widget. Thanks!
This code throws the error because you try to cast a String to a Future<String>>, although it is a String.
Future<String> _textLine = '' as Future<String>;
If you want to declare a Future with a value, you can use the value method.
Future<String> _textLine = Future.value('');
In a Future Builder im trying to use two methods with different types, both of them fetch data from the api,
the main problem that im having is that both of the function have different types, so im having problem on putting
the two methods because of their types. I tried using
Future.wait(Future[]) aswell but i was getting many errors, there errors where mostly on List,
im still trying to learn how Future Builders work, i worked with FutureBuilders before but didnt have to use two functions inside the FutureBuilder. So if anyone could implement their solution on my code, that would really help and maybe add some comments on why did you make the change so i learn for the future. As a bonus im getting the List is not a subtype of type Map<String, dynamic> error aswell so if anyone could help with that too it would be very helpful. Tried looking into stack over flow answers for that but i couldnt figure it out since i was getting an error on this part
buildSwipeButton() {
return MenuPage(
sendData: fetchLoginData()// i was getting error here,
);
}
buildSwipeButton() {
return MenuPage( // other class name from a different file
sendData: fetchLoginData(),
);
}
buildSwipeButton2() {
return MenuPage( // other class name from a different file
sendData2: fetchWorkingLocationData(),
);
}
Future<LoginData>? fetchLoginData() async {
var url = 'https://dev.api.wurk.skyver.co/api/employees';
String basicAuth = 'Basic ' +
base64Encode(
utf8.encode('${emailController.text}:${passwordController.text}'),
);
var response = await http.get(
Uri.parse(url),
headers: <String, String>{'authorization': basicAuth},
);
print(response.body);
if (response.statusCode == 200) {
print(response.statusCode);
return LoginData.fromJson(
jsonDecode(response.body),
);
} else {
throw Exception('Failed to load LoginData');
}
}
Future<WorkingLocationData>? fetchWorkingLocationData() async {
var url = 'https://dev.api.wurk.skyver.co/api/locations';
String basicAuth = 'Basic ' +
base64Encode(
utf8.encode('${emailController.text}:${passwordController.text}'),
);
var response2 = await http.get(
Uri.parse(url),
headers: <String, String>{'authorization': basicAuth},
);
print(response2.body);
if (response2.statusCode == 200) {
print(response2.statusCode);
return WorkingLocationData.fromJson(
jsonDecode(response2.body),
);
} else {
throw Exception('Failed to load Working Location Data');
}
}
// other file where im trying to use Future Builder
late LoginData data;
Future<LoginData>? sendData;
Future<WorkingLocationData>? sendData2;
body: FutureBuilder<LoginData>(
future: sendData, // trying to use sendData and sendData2
builder: (context, snapshot) {
if (snapshot.hasData) {
LoginData? data1 = snapshot.data;
data = data1!;
print(data.loginPhoneNumber);
return afterLoginBody();
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return Center(child: const CircularProgressIndicator());
},
),
),
Try below code hope its help to you.
late LoginData data;
Future<LoginData>? sendData;
Future<WorkingLocationData>? sendData2;
body: FutureBuilder<LoginData>(
Future.wait([sendData, sendData2]),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot{
snapshot.data[0]; //sendData
snapshot.data[1]; //sendData2
},
),
),
You can try this:
Future? _future;
Future<dynamic> sendData() async {
final data1 = await sendData1();
final data2 = await sendData2();
return [data1, data2];
}
#override
void initState() {
_future = sendData()();
super.initState();
}
///
FutureBuilder(
future: _future,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CupertinoActivityIndicator();
}
if (snapshot.hasError) {
return SomethingWentWrong();
}
final data1= snapshot.data[0] as YourDataModel;
final data2 = snapshot.data[1] as YourDataModel;
});
I am simply trying to set an ID in this function:
_getLastWorkoutId() async {
try {
var snapshot = await usersRef
.doc(currentUser!.uid)
.collection('workouts')
.orderBy('workoutDate', descending: true)
.limit(1)
.snapshots()
.first;
//The execution moves to build method from here------and then returns
for (var element in snapshot.docs) {
workoutId = element.id;
setState(() {
_isWorkoutIdSet = true;
});
}
//return snapshot;
} catch (e) {
print(e.toString());
}
//return null;
}
I call it in the initState:
#override
void initState() {
WidgetsBinding.instance!.addObserver(this);
super.initState();
//var snapshot = _getLastWorkoutId();
_getLastWorkoutId();
}
The problem is, the for loop executes after the build function is called. I don't want that to happen.
You can use FutureBuilder like this:
Future<bool> _value;
#override
void initState() {
WidgetsBinding.instance!.addObserver(this);
super.initState();
_value = _getLastWorkoutId();
}
And in your build method you have:
FutureBuilder<bool>(
future: _value,
builder: (
BuildContext context,
AsyncSnapshot<bool> snapshot,
) {
if (snapshot.hasData) {
if (snapshot.data){
//update view
}else{
//update view
}
}
}
The method can be like this:
_getLastWorkoutId() async {
try {
var snapshot = await usersRef
.doc(currentUser!.uid)
.collection('workouts')
.orderBy('workoutDate', descending: true)
.limit(1)
.snapshots()
.first;
for (var element in snapshot.docs) {
workoutId = element.id;
return true;
}
} catch (e) {
print(e.toString());
}
}
Here you can find more about FutureBuilder.
I believe this should solve the issue:
First, on build method:
return FutureBuilder(
future: _getLastWorkoutId(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) return CircularProgressIndicator();
return Container(); // here goes whatever it is you had before.
}
);
then on _getLastWorkoutId():
Future<void> _getLastWorkoutId() async {
...
}
That way the function returns a future of void instead of void, allowing FutureBuilder to do its thing.
You should declare a variable to save state. For example
var isLoading = true;
At the end of try block in func _getLastWorkoutId reset isLoading to false.
Then call setState or update state by better way used state management likes provider, bloc, get.
In build widget add check isLoading like this
return isLoading ? YourLoadingIndicatorWidget() : DisplayDataWidget();
I have a function called getAllUsers() that returns all users from a database. The problem is that I want GridView.builder() to display all the users except the current user, but despite all the research I did, nothing seems to work out.
If i use the if condition like if(snapshot.data.documents[i].data["username"] != currentUserId within itemBuilder:, it returns a blank tile which represents the current user which creates a gap within the grid view. Thus, it makes the grid view look really bad.
I believe this problem could have been solved if I knew how to include the inequality query in the getAllUsers() method. But my understanding is that Firestore has yet to provide this function/argument.
HomeFragment class
Database _database = Database();
Stream _stream;
String currentUserId;
#override
void initState() {
getCurrentUserId();
getAllUsers();
super.initState();
}
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted)
setState(() => _stream = val);
});
}
getCurrentUserId() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
currentUserId = currentUser.uid;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
return snapshot.data == null ? Center(child: CircularProgressIndicator())
: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child:
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: snapshot.data.documents.length,
itemBuilder: (context, i) {
return Container(
child: Text(snapshot.data.documents[i].data["username"])
);
}
// etc etc..
Database class
getAllUsers() async {
return await _firestore.collection("users").snapshots();
}
I tried to use this, but _stream2 returns null
Stream _stream, _stream2;
getAllUsers() async {
return await _database.getAllUsers().then((val) {
if (mounted) {
List<String> list;
setState(() {
_stream = val;
_stream2 = _stream.where((snapshot) {
_querySnapshot = snapshot;
for (int i = 0; i < _querySnapshot.documents.length; i++)
list.add(_querySnapshot.documents[i].data["userId"]);
return list.contains(currentUserId) == false;
});
});
}
});
}
I also tried this, it is not working
getAllUsers() async {
Stream<QuerySnapshot> snapshots = await _database.getAllUsers();
_stream = snapshots.map((snapshot) {
snapshot.documents.where((documentSnapshot) {
return documentSnapshot.data["userId"] != currentUserId;
});
});
}
Maybe you can try something like this. You filter the query result:
getAllUsers() async {
final Stream<QuerySnapshot> snapshots = await _firestore.collection("users").snapshots();
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => User.fromMap(snapshot.data)
.where((user) => user.id != currentUser.id)
.toList();
return result;
}
}
If you do not have an User class, you can replace some lines with this. But the result will be a list of Map<String, dynamic> instead of a list of User objects.
return snapshots.map((snapshot) {
final result = snapshot.documents
.map((snapshot) => snapshot.data
.where((user) => user['id'] != currentUser.id)
.toList();
return result;
This solution worked well for me.
firestore.collection('your collection').where('x', isNotEqualTo: auth.currentUser!.uid).snapshots();
from this link on my web server as
http://instamaker.ir/api/v1/getPersons
i'm trying to get result and printing avatar from that result, unfortunately my implementation with rxDart and Bloc don't get result from this response and i don't get any error
server response this simplified result:
{
"active": 1,
"name": "my name",
"email": " 3 ",
"loginType": " 3 ",
"mobile_number": " 3 ",
...
"api_token": "1yK3PvAsBA6r",
"created_at": "2019-02-12 19:06:34",
"updated_at": "2019-02-12 19:06:34"
}
main.dart file: (click on button to get result from server)
StreamBuilder(
stream: bloc.login,
builder: (context,
AsyncSnapshot<UserInfo>
snapshot) {
if (snapshot.hasData) {
parseResponse(snapshot);
}
},
);
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.avatar);
}
LoginBlock class:
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async{
UserInfo userInfo = await _repository.userInfo();
_login_fetcher.sink.add(userInfo);
}
dispose(){
_login_fetcher.close();
}
}
final bloc = LoginBlock();
Repository class:
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin();
}
my model:
class UserInfo {
int _active;
String _name;
...
UserInfo.fromJsonMap(Map<String, dynamic> map)
: _active = map["active"],
_name = map["name"],
...
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['active'] = _active;
data['name'] = _name;
...
return data;
}
//GETTERS
}
BaseUrl class:
class BaseUrl {
static const url = 'http://instamaker.ir';
}
and then InstagramApiProviders class:
class InstagramApiProviders {
Client client = Client();
Future<UserInfo> checkUserLogin() async {
final response = await client.get(BaseUrl.url+'/api/v1/getPersons');
print("entered "+BaseUrl.url+'/api/v1/getPersons');
if (response.statusCode == 200) {
return UserInfo.fromJsonMap(json.decode(response.body));
} else
throw Exception('Failed to load');
}
}
Well the answer here is part of the test that I make to get this done. I can put my all test here but I think that the problem cause was because as StreamBuilder is a widget his builder method callback is only called when the widget is in flutter widget tree. As in your sample you're just creating a StreamBuilder the builder method will never be called bacause this widget isn't in widget tree.
As advice first test your code changing only UI layer... do somenthing like:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
Here we're putting the StreamBuilder in widget tree so the builder callback is called and maybe you will see the results. If it fails, please comment that I update my answer with my full test code with this working.
Updating the answer with sources that I made tests.
Basic model
class UserInfo {
int _active;
String name;
UserInfo.fromJsonMap(Map<String, dynamic> map) {
_active = map["active"];
name = map["name"];
}
Map<String, dynamic> toJson() => {
'active' : _active,
'name' : name,
};
}
The provider class
class InstagramApiProviders {
Future<UserInfo> checkUserLogin() async {
UserInfo info;
try {
http.Response resp = await http.get("http://instamaker.ir/api/v1/getPersons");
if (resp.statusCode == 200){
print('get response');
print( resp.body );
info = UserInfo.fromJsonMap( Map.from( json.decode(resp.body ) ));
}
}
catch (ex) {
throw ex;
}
print('returning $info');
return info;
}
}
Repository
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin().then((user) => user);
}
BLoC class
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async {
UserInfo info = await _repository.userInfo();
_login_fetcher.sink.add(info);
}
dispose(){
_login_fetcher.close();
}
}
Widget UI
This starts showing There is no data message but when you hit appBar button wait a little and then the data is fetched and updates the UI.
class WidgetToShowData extends StatelessWidget {
final LoginBlock bloc = LoginBlock();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
}
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.name);
}
}