Flutter Provider Mixing Local State with Global Change Notifier - flutter

I have a bit of a philosophical question here regarding providers.
I have a user provider as such:
#JsonSerializable(explicitToJson: true)
class ZUser extends ChangeNotifier {
final String uid;
String? displayName;
String? email;
String? phoneNumber;
String? photoURL;
String? did;
List<String>? interests = [];
#JsonKey(ignore: true)
Database _db = Database();
ZUser({required this.uid}) {
Database().getUser(uid).listen((user) async {
displayName = user?.displayName;
email = user?.email;
phoneNumber = user?.phoneNumber;
photoURL = user?.photoURL;
did = user?.did;
interests = user?.interests;
notifyListeners();
});
}
Future addInterest(String interest) async {
interests ??= [];
if (!interests!.contains(interest)) {
interests!.add(interest);
return _db.updateUser(uid, {"interests": interests});
}
}
Future removeInterest(String interest) async {
interests ??= [];
if (interests!.contains(interest)) {
interests!.remove(interest);
return _db.updateUser(uid, {"interests": interests});
}
}
factory ZUser.fromJson(Map<String, dynamic> json) => _$ZUserFromJson(json);
Map<String, dynamic> toJson() => _$ZUserToJson(this);
}
Notice that I listen on DB changes with a stream, and then notify listeners.
Now I have a local class that I want to listen to interests for the user. In this class, I want to show a loading indicator when an item is selected/deselected, and then remove said indicator when the item syncs with the DB. I tried this, but I run into race condition issues, and many times the indicator persists far longer than it took to sync with the DB;
class _HomeState extends State<Home> {
bool generalLoading = false;
static const String generalStr = "GENERAL";
#override
Widget build(BuildContext context) {
var zuser = Provider.of<ZUser?>(this);
return zuser == null
? const Loading()
: Scaffold(
backgroundColor: context.backgroundColor,
appBar: const PreferredSize(
preferredSize: Size.fromHeight(Constants.BarHeight),
child: ZLandingMenuBar(),
),
body: Column(
mainAxisAlignment: context.isMobile
? MainAxisAlignment.start
: MainAxisAlignment.center,
children: [
BlockContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ZCheckBoxTile(
title: "General Interests",
loading: generalLoading,
value: zuser.interests?.contains(generalStr),
onPressed: () {
if (generalLoading) return;
setState(() {
generalLoading = true;
});
zuser.interests != null &&
zuser.interests!.contains(generalStr)
? zuser.removeInterest(generalStr).whenComplete(
() => setState(() {
generalLoading = false;
}),
)
: zuser.addInterest(generalStr).whenComplete(
() => setState(() {
generalLoading = false;
}),
);
},
),
context.sd,
],
),
),
],
),
);
}
}
I want to remove the loading if and only if I get an update from the provider (and not for any other build of the widget). Any ideas for a cleaner way to do this?

Related

Display data from Firebase in async - Flutter

I want to create a profil page where I just display informations from the user, but I have trouble to reach the data. When I want to use my variable user it display 'Instance of Future<Map<String, dynamic>>'
If I put the 'Widget build' in async I have an error message who told me : ProfileScreen.build' ('Future Function(BuildContext)') isn't a valid override of 'StatelessWidget.build' ('Widget Function(BuildContext)').
class ProfileScreen extends StatelessWidget {
ProfileScreen({super.key});
#override
Widget build(BuildContext context) {
final user = displayUser();
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.topLeft,
child: Column(children: [
Text('Prénom :${user}\nNom :\nEmail :',
textWidthBasis: TextWidthBasis.longestLine),
]),
)
]),
),
persistentFooterButtons: [
SignOutButton(),
BottomNavBar(),
]);
}
// Get user informations
Future<Map<String, dynamic>> displayUser() async {
final User? currentUser = FirebaseAuth.instance.currentUser;
late final userUid = currentUser?.uid;
late final ref = FirebaseDatabase.instance.ref();
final resSnapshot = await ref.child('/utilisateur/' + userUid!).get();
final Map<String, dynamic> user = {};
if (resSnapshot.exists) {
user['id'] = userUid;
for (var value in resSnapshot.children) {
String key = value.key as String;
var val = value.value;
user[key] = val;
}
} else {
print('No data available.');
}
print(user); // This print display exactly the informations I want.
return user;
}
}
Thanks for your help.
Your displayUser is async function and you can't call it inside build method, you need to use FutureBuilder like this:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: FutureBuilder<Map<String, dynamic>>(
future: displayUser(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
Map<String, dynamic> user = snapshot.data ?? {};
return Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.topLeft,
child: Column(
children: [
Text(
'Prénom :${user}\nNom :\nEmail :',
textWidthBasis: TextWidthBasis.longestLine,
),
],
),
)
],
),
);
}
}
},
),
persistentFooterButtons: [
SignOutButton(),
BottomNavBar(),
],
);
}
You can customize loading and error state to what you want.
You can load the user in the initstate and then set user using setstate
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
#override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
Map<String, dynamic>? user;
#override
void initState() {
final User? currentUser = FirebaseAuth.instance.currentUser;
late final userUid = currentUser?.uid;
late final ref = FirebaseDatabase.instance.ref();
final resSnapshot = await ref.child('/utilisateur/' + userUid!).get();
Map<String, dynamic> temp = {};
if (resSnapshot.exists) {
temp['id'] = userUid;
for (var value in resSnapshot.children) {
String key = value.key as String;
var val = value.value;
temp[key] = val;
}
} else {
print('No data available.');
}
print(temp);
setState((){
user =temp
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
user != {} ? Text(user.toString()!) : const CircularProgressIndicator()),
);
}
}
change StatelessWidget to StatefulWidget because userInteract on profileScreen,
UserInteraction changes will show on firebase.
class ProfileScreen extends StatefulWidget{
ProfileScreen({super.key});

E/LB (26008): fail to open file: No such file or directory -Flutter

I try to get a List from this Api(https://www.getpostman.com/collections/fa1296508e65891de558)
But there does no appear any Object. Console showing => "E/LB (26008): fail to open file: No such file or directory
".
I tried to print respone.statusCode but the result does'n apper in console.
I hope to solve this problem, Thank you.
What can be the problem here?
My code:
class ApiSetting{
static const String _baseUri='http://demo-api.mr-dev.tech/api/';
static const String users= '${_baseUri}users';
}
**User Model
** class User {
late int id;
late String firstName;
late String lastName;
late String email;
late String mobile;
late String bio;
late String jobTitle;
late String latitude;
late String longitude;
late String country;
late String image;
late String active;
late String emailVerifiedAt;
late String imagesCount;
User.fromJson(Map<String, dynamic> json) {
id = json['id'];
firstName = json['first_name'];
lastName = json['last_name'];
email = json['email'];
mobile = json['mobile'];
bio = json['bio'];
jobTitle = json['job_title'];
latitude = json['latitude'];
longitude = json['longitude'];
country = json['country'];
image = json['image'];
active = json['active'];
emailVerifiedAt = json['email_verified_at'];
imagesCount = json['images_count'];
}
}
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:api_secand_project/api/api_setting.dart';
import 'package:api_secand_project/models/user.dart';
class UserApiController {
Future<List<User>> getUser() async {
var uri = Uri.parse(ApiSetting.users);
var response = await http.get(uri);
if (response.statusCode == 200) {
print(response.statusCode);
var jsonResponse = jsonDecode(response.body);
var userJsonArray = jsonResponse['data'] as List;
return userJsonArray
.map((jsonObject) => User.fromJson(jsonObject))
.toList();
}
return [];
}
}
import 'package:api_secand_project/api/controllers/user_api_controller.dart';
import 'package:api_secand_project/models/user.dart';
import 'package:flutter/material.dart';
class UsersScreen extends StatefulWidget {
const UsersScreen({Key? key}) : super(key: key);
#override
State<UsersScreen> createState() => _UsersScreenState();
}
class _UsersScreenState extends State<UsersScreen> {
List<User> _users=<User>[];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Users'),
),
body: FutureBuilder<List<User>>(
future: UserApiController().getUser(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return const Center(
child: CircularProgressIndicator(),
);
}
else if(snapshot.hasData){
_users=snapshot.data!;
return ListView.builder(
itemCount: _users.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
radius: 30,
// child: NetworkImage(snapshot.data!.),
),
title: Text(_users[index].firstName),
subtitle: Text(_users[index].mobile),
);
},
);
}
else{
return Center(child: Text('No Data',style: TextStyle(fontWeight: FontWeight.bold,fontSize: 28),),);
}
},
));
}
}
The question was not very clear, and there is no clear screenshot or message from the error console,
It seems that you are using the BLOC pattern and since part of the code is missing, you decide to create one from scratch, maybe it will help you, I thought not to publish it, but maybe something from here will help you
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class GetApi extends StatefulWidget {
const GetApi({super.key});
#override
State<GetApi> createState() => _GetApiState();
}
class _GetApiState extends State<GetApi> {
List<User> users = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Get Api")),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton(
onPressed: () {
getApi();
},
child: const Text("Get Api"),
),
Flexible(
child: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
User user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.id),
);
}),
),
],
),
);
}
Future<void> getApi() async {
users = [];
Uri uri = Uri.parse("https://www.getpostman.com/collections/fa1296508e65891de558 ");
http.Response response = await http.get(uri);
if (response.statusCode == 200) {
//debugPrint("body: ${response.body}");
Map data = jsonDecode(response.body);
for (MapEntry item in data.entries) {
//debugPrint("key: ${item.key} value: ${item.value}");
if ("item" == item.key) {
List usersResponse = data["item"];
//debugPrint("users: ${users}");
for (dynamic json in usersResponse) {
User user = User.fromJson(json);
users.add(user);
//debugPrint("user: ${_user.name}");
}
}
}
if (!mounted) return;
setState(() {});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("succes -> status: ${response.statusCode}"),
backgroundColor: Colors.green,
),
);
} else {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("fail -> status: ${response.statusCode}"),
backgroundColor: Colors.red,
),
);
}
}
}
class User {
late String name;
late String id;
User.fromJson(Map<String, dynamic> json) {
name = json['name'];
id = json['id'];
}
}

How to display a list of map in firestore and shows them in flutter

I have the below model:
class UserInfoModel {
String? image;
String? name;
String? country;
String? city;
String? position;
List<UserSkills>? userSkills;
UserInfoModel(
{this.image, this.name, this.country, this.position, this.userSkills});
UserInfoModel.fromJson(dynamic json) {
image = json['user_image'];
name = json['name'];
country = json['country'];
city = json['city'];
position = json['position'];
userSkills = [
for (final skill in json['skills'] ?? []) UserSkills.fromJson(skill),
];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['user_image'] = this.image;
data['name'] = this.name;
data['country'] = this.country;
data['city'] = this.city;
data['position'] = this.position;
data['skills'] = [for (final skill in this.userSkills ?? []) skill.toJson()];
return data;
}
}
class UserSkills {
String? skillName;
String? skillPerc;
UserSkills({this.skillName, this.skillPerc});
UserSkills.fromJson(dynamic json) {
skillName = json['skill_name'];
skillPerc = json['skill_perc'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['skill_name'] = this.skillName;
data['skill_perc'] = this.skillPerc;
return data;
}
}
which is related to below image fire store:
I tried to create two methods one reads the user Info and the other method supposed to read the user skills, so here's the below code I have:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:haroonpf/enums/screen_state.dart';
import 'package:haroonpf/presentation/screens/home/models/user_info.dart';
import 'package:haroonpf/utils/constants.dart';
import '../../base_view_model.dart';
class HomeViewModel extends BaseViewModel {
UserInfoModel? userModel;
List<UserSkills> userSkills = [];
void getUserData() async {
await FirebaseFirestore.instance
.collection('users')
.doc(uId)
.get()
.then((value) {
// print("fbValues: " + value.data().toString());
userModel = UserInfoModel.fromJson(value.data());
}).catchError((error) {
print(error.toString());
});
setState(ViewState.Idle);
}
Future<List<UserSkills>> getUserSkills() async {
CollectionReference getSkills =
FirebaseFirestore.instance.collection('users');
await getSkills.get().then((snapshot) {
if (userSkills.isNotEmpty) userSkills.clear();
snapshot.docs.forEach((element) {
userSkills.add(UserSkills.fromJson(element.data()));
print("my skills:" + element.data().toString());
});
});
setState(ViewState.Idle);
return userSkills;
}
}
so in my Skills widget class I tried to retrieve data as the below code:
import 'package:flutter/material.dart';
import 'package:haroonpf/presentation/screens/home/viewmodel/home_view_model.dart';
import 'package:haroonpf/utils/animation/animated_progress_indicator.dart';
import 'package:haroonpf/utils/constants.dart';
import '../../../../../base_screen.dart';
class Skills extends StatelessWidget {
const Skills({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BaseScreen<HomeViewModel>(onModelReady: (homeViewModel) {
homeViewModel.getUserSkills();
}, builder: (context, homeViewModel, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: defaultPadding),
child: Text(
"Framework out skills",
style: Theme.of(context).textTheme.subtitle2,
),
),
SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Row(
children: [
...homeViewModel.userSkills.map(
(skills) => Expanded(
child: AnimatedCircularProgressIndicator(
percentage: double.parse(skills.skillPerc!),
label: skills.skillName.toString(),
),
),
),
],
),
),
],
);
});
}
}
but I found the below error:
Unexpected null value.
So it seems that because I didn't refers tot the doc id, as I tried to add the doc id it doesn't work...
So how can I retrieve the skills data to the AnimatedCircularProgressIndicator correctly?
Unexpected null value.
Do you know what line is causing this error?
I'm not sure I understand your approach to the HomeViewModel class. If you modify getUserData to return a Future<UserInfoModel> then you could just pass it to a FutureBuilder in your Skills widget:
class HomeViewModel extends BaseViewModel {
UserInfoModel? userModel;
Future<UserInfoModel> getUserData() async {
// not sure what setState does here but it was in the function to begin with
setState(ViewState.Idle);
// here I am returning a cached userModel.
// remove the following line of code if you don't actually want caching.
if (userModel != null) return userModel;
final value =
await FirebaseFirestore.instance.collection('users').doc(uId).get();
userModel = UserInfoModel.fromJson(value.data());
return userModel;
}
}
And then in your Skills widget, create the FutureBuilder, and then loop over the List<UserSkills> within your UserInfoModel.
class Skills extends StatelessWidget {
const Skills({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BaseScreen<HomeViewModel>(onModelReady: (homeViewModel) {
// homeViewModel.getUserSkills();
}, builder: (context, homeViewModel, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: defaultPadding),
child: Text(
"Framework out skills",
style: Theme.of(context).textTheme.subtitle2,
),
),
FutureBuilder<UserInfoModel>(
future: homeViewModel.getUserData(),
builder: (context, snapshot) => SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Row(
children: [
// loop over the skills to display each one
for (final skill in snapshot.data?.userSkills ?? [])
Expanded(
child: AnimatedCircularProgressIndicator(
percentage: double.parse(skill.skillPerc!),
label: skill.skillName.toString(),
),
),
],
),
),
),
],
);
});
}
}

Flutter Listview builder is blank until hot reload

I am using mobx and have split the code into 2 indvidualdata and indvidualdata provider and there is a autogenerated code with mobx.
The Listview.builder does not load the data until I hot reload the code (VSCode).
class IndividualDataState extends State<IndividualData> {
#override
void initState() {
super.initState();
setup();
sl<IIndividualDataProvider>()?.initReporting(context);
}
#override
Widget build(BuildContext context) {
return Observer(
builder: (_) => Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
elevation: 0,
centerTitle: true,
title: Text("All Data"),
backgroundColor: PRIMARY,
),
body: Stack(
children: <Widget>[
ListView.builder(
itemCount: sl<IIndividualDataProvider>().entries.length == null
? 0
: sl<IIndividualDataProvider>().entries.length,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text(sl<IIndividualDataProvider>()
.entries[index]
.entry
.toString()),
subtitle: new Text(sl<IIndividualDataProvider>()
.entries[index]
.createdAt
.toString()),
);
},
),
],
),
),
);
}
}
The provider
class IndividualDataProvider = IIndividualDataProvider
with _$IndividualDataProvider;
abstract class IIndividualDataProvider with Store {
#observable
bool isLoading = false;
#observable
List tags = [];
#observable
List<IndvidualReadings> entries = [];
#action
Future initReporting(context) async {
try {
isLoading = true;
Response _readings = await sl<IIndividualDataService>().getAllRmssd();
Map<String, dynamic> map = _readings.data;
List<dynamic> data = map["readings"];
if (data != null) {
data.forEach((v) {
IndvidualReadings tempRead = IndvidualReadings.fromJson(v);
entries.add(tempRead);
});
}
isLoading = false;
} catch (err) {
isLoading = false;
print(err.toString());
}
}
}
class IndvidualReadings {
double entry;
String createdAt;
List<ReadingTags> tags = [];
IndvidualReadings({this.entry, this.createdAt, this.tags});
factory IndvidualReadings.fromJson(Map<String, dynamic> json) {
var list = json['tags'] as List;
print(list.runtimeType);
List<ReadingTags> tagsList =
list.map((i) => ReadingTags.fromJson(i)).toList();
return IndvidualReadings(
entry: json['entry'], createdAt: json['created_at'], tags: tagsList);
}
}
class ReadingTags {
int id;
String tagName;
ReadingTags({this.id, this.tagName});
ReadingTags.fromJson(Map<String, dynamic> json) {
id = json['id'];
tagName = json['tag_name'];
}
}
When I click to open the page, it is blank. I had a few prints to see if the data is being pulled by the API and it was printing successfully.
Then when I just hot reload (I usually press Ctrl+S) the information is loaded correctly and the ListTile is rendered.
I am completely lost for words why this happens. Any help is appreciated.
You entries should be an ObservableList - then only the Observer widget will rebuild the changes in entries list automatically.
...
#observable
ObservableList<IndvidualReadings> entries = ObservableList;
...

Flutter HTTP POST Request with TextField Controller

I am trying to make http post request with 2 var as my parameter. If I type the first and second var (into my textfield) and then I click Submit, it will do http request. It works, but I'm trying to re-format/parse the response into List.
Future<Repair> getRepair(String Id, String title) async {
final String apiUrl =
"*****";
final response =
await http.post(apiUrl, body: {"id": Id, "title": title});
if (response.statusCode == 200) {
final String responseString = response.body;
return repairModelFromJson(responseString);
} else {
print(null);
return null;
}
}
class _MainFetchDataState extends State<MainFetchData> {
Repair _repair;
final TextEditingController caseController = TextEditingController();
final TextEditingController serialController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Fecth"),
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text("Submit"),
onPressed: () async {
final String Id = idController.text;
final String title = titleController.text;
final Repair repair = await getRepair(Id, title);
setState(() {
_repair = repair;
});
},
),
),
body: Column(
children: <Widget>[
TextField(
controller: idController,
),
TextField(
controller: titleController,
),
SizedBox(
height: 32,
),
_repair == null
? Container()
: Text(_repair.movement.toString() != null ? "${_repair.movement.toString()}" : 'Vuoto'),
],
),
);
}
}
and
import 'dart:convert';
Repair repairModelFromJson(String str) => Repair.fromJson(json.decode(str));
String repairModelToJson(Repair data) => json.encode(data.toJson());
class Repair {
String Id;
String title;
String movement;
Repair({
this.Id,
this.title,
this.movement,
});
factory Repair.fromJson(Map<String, dynamic> json) => Repair(
Id: json["id"],
title: json["title"],
movement: json['movement'].toString(),
);
Map<String, dynamic> toJson() => {
"id": Id,
"title": title,
"movement": movement,
};
}
Now, I show this (image) and i would like to show the response like a List.
image