Flutter Listview builder is blank until hot reload - flutter

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;
...

Related

Loading listview from an api Layer

I am trying to load a listview using flutter and dart but am having an issue, bare with me am new to flutter and learning by example https://github.com/didinj/flutter-crud-restapi-example/blob/master/lib/caseslist.dart am coming from a c# background. I obfuscated my api url to protect it it is valid my side.
class PlayerList extends StatelessWidget {
final List<Player> players;
PlayerList({Key key, this.players}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: players == null ? 0 : players.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {},
child: ListTile(
leading: Icon(Icons.person),
title: Text(players[index].firstName),
subtitle: Text(players[index].surname.toString()),
),
));
});
}
}
The issue surrounds this line.
PlayerList({Key key, this.players}) : super(key: key);
It says key does not exist.
I am loading the list view as such?.
#override
Widget build(BuildContext context) {
if (players == null) {
players = api.getAllPlayers() as List<Player>;
}
return Scaffold(
appBar: AppBar(
title: const Text("Flutter ListView"),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: new Center(
child: new FutureBuilder(
future: loadList(),
builder: (context, snapshot) {
return players.length > 0
? new PlayerList(players: players)
: new Center(
child: new Text('No data found, tap plus button to add!',
style: Theme.of(context).textTheme.titleLarge));
},
)),
));
}
Future loadList() {
Future<List<Player>> playersApi = api.getAllPlayers();
playersApi.then((PlayerList) {
setState(() {
this.players = PlayerList;
});
});
return playersApi;
}
}
My Api Call is
class ApiService {
final String apiUrl = "https://secreturl/api";
final String getAllPlayersEndPoint = "/GetAllPlayers/";
Future<List<Player>> getAllPlayers() async {
final getallPlayersUrl = Uri.parse(apiUrl + getAllPlayersEndPoint);
Response res = await get(getallPlayersUrl);
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<Player> players =
body.map((dynamic item) => Player.fromJson(item)).toList();
return players;
} else {
throw "Failed to load cases list";
}
}
}
This is my Model
class Player {
final int id;
final int type;
final String playerLevel;
final String firstName;
final String surname;
Player(this.id, this.type, this.playerLevel, this.firstName, this.surname);
factory Player.fromJson(Map<String, dynamic> json) {
return Player(
json['id'],
json['type'],
json['playerlevel'],
json['firstname'],
json['surname'],
);
}
#override
String toString() =>
'Players{id: $id, firstName: $firstName, lastName: $surname}';
}

Flutter Provider Mixing Local State with Global Change Notifier

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?

removeWhere() method does not remove the data

I am building a food recipe app where user can browse various recipes.
The functionality is that, when user hit delete button, the item will not be shown in listing. I navigated the the mealId to the previous screen, i.e. Listing screen through
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).pop(mealId);
},
child: const Icon(Icons.delete),
),
I receive the pop() value in backward widget like:
void selectMeal(BuildContext context) {
Navigator.of(context)
.pushNamed(MealsDetailsScreen.routeName, arguments: id)
.then((result) {
if (result != null) {
removeItem(result);
print(result); // it prints the expected id
}
});
}
And in the code attached fully, I wanted to remove the item details via mealId
void _removeMeal(String mealId) {
setState(() {
print("$mealId from didChangedDependancies"); //it also prints the expected id
displayedMeals.removeWhere((meal) => meal.id == mealId);
});
}
The code where I set the function to remove:
import 'package:flutter/material.dart';
import '../models/meals.dart';
import '../models/dummy_data.dart';
import '../widgets/meal_item.dart';
class CategoryMealaScreen extends StatefulWidget {
static const routeName = '/category-meals';
#override
State<CategoryMealaScreen> createState() => _CategoryMealaScreenState();
}
class _CategoryMealaScreenState extends State<CategoryMealaScreen> {
late String categoryTitle;
late List<Meal> displayedMeals;
var _loadedInitData = false;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (!_loadedInitData) {
final routeArgs =
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
categoryTitle = routeArgs['title'].toString();
final categoryId = routeArgs['id'];
displayedMeals = dummyMeals.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
_loadedInitData = true;
}
super.didChangeDependencies();
}
void _removeMeal(String mealId) {
setState(() {
print("$mealId from didChangedDependancies");
displayedMeals.removeWhere((meal) => meal.id == mealId);
});
}
#override
Widget build(BuildContext context) {
final routeArgs = // received data from widget CategoryItems()
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
final categoryTitle = routeArgs['title'];
final categoryId = routeArgs['id'];
final displayedMeals = dummyMeals.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
return Scaffold(
appBar: AppBar(
title: Text(categoryTitle.toString()),
),
body: ListView.builder(
itemCount: displayedMeals.length,
itemBuilder: (ctx, index) {
return MealItem(
id: displayedMeals[index].id,
title: displayedMeals[index].title,
imageUrl: displayedMeals[index].imageUrl,
complexity: displayedMeals[index].complexity,
affordability: displayedMeals[index].affordability,
duration: displayedMeals[index].duration,
removeItem: _removeMeal,
);
}),
);
}
}
No error shows on console.
I'll be vary happy if you guys help me out! Thanks a lot😎
Remove final displayedMeals inside your build method.
Use the displayedMeals variable outside your build method instead.

How to add List Item to FutureBuilder ListView without reloading the data from remote server? [Flutter]

I'm new in flutter, I'd like to know how to add an item list dynamically to ListView without reloading data in FutureBuilder.
When I add an item to the ListView, it duplicate the list and then added the item to that list.
The Following code, include Model clas called Job.
JobListView is a stateful widget that include the dynamic ListView.
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
class Job {
#required
String company;
String description;
String employmentType;
int id;
String location;
String position;
List<String> skillsRequired;
Job(
this.company,
this.description,
this.employmentType,
this.id,
this.location,
this.position,
this.skillsRequired);
Job.fromJson(Map<String, dynamic> json) {
company = json['company'];
description = json['description'];
employmentType = json['employmentType'];
id = json['id'];
location = json['location'];
position = json['position'];
if (json['skillsRequired'] != null) {
skillsRequired = new List<String>();
json['skillsRequired'].forEach((v) {
skillsRequired.add(v);
});
}
}
}
class JobListView extends StatefulWidget {
#override
_JobListViewState createState() => _JobListViewState();
}
class _JobListViewState extends State<JobListView> {
List<Job> data = List<Job>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Job>>(
future: _getJob(),
builder: (context, snapshot) {
if (snapshot.hasData) {
data = snapshot.data;
return _listViewFormat(data);
} else if (snapshot.hasError) {
return Container();
}
return Center(
child: Container(
width: 50,
height: 50,
child: CircularProgressIndicator(),
),
);
},
) ,
floatingActionButton: (FloatingActionButton(child: Icon(Icons.add),onPressed: (){
setState(() {
var j = Job("CompanyX","Eng.5 position","Full-time",0,"Cairo","Senior",null);
data.add(j);
});
},)),
);
}
}
ListView _listViewFormat(List<Job> data) {
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return _tile(data[index].position, data[index].description, Icons.work);
});
}
ListTile _tile(String title, String subtitle, IconData iconData) {
return ListTile(
title: Text(title, style: TextStyle(fontSize: 20)),
subtitle: Text(
subtitle,
style: TextStyle(fontSize: 12),
),
leading: Icon(iconData),
trailing: Icon(Icons.arrow_right),
);
}
Future<List<Job>> _getJob() async {
String baseUrl = 'https://mock-json-service.glitch.me';
var response = await get(baseUrl);
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((job) => new Job.fromJson(job)).toList();
}
}
Check out this more explanation How to deal with unwanted widget build?
if future changes you will see changes
Move _getJob method inside initState like this:
class _JobListViewState extends State<JobListView> {
List<Job> data = List<Job>();
Future<List<Job>> getJobFuture;
#override
void initState() {
super.initState();
getJobFuture = _getJob();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Job>>(
future: getJobFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
data = snapshot.data;
return _listViewFormat(data);
} else if (snapshot.hasError) {
return Container();
}
return Center(
child: Container(
width: 50,
height: 50,
child: CircularProgressIndicator(),
),
);
},
) ,
floatingActionButton: (FloatingActionButton(child: Icon(Icons.add),onPressed: (){
setState(() {
var j = Job("CompanyX","Eng.5 position","Full-time",0,"Cairo","Senior",null);
data.add(j);
});
},)),
);
}
}

type 'Future<dynamic>' is not a subtype of type 'List<Profile>

class Profile {
final List<String> photos;
final String name;
final int age;
final String education;
final String bio;
final int distance;
Profile({
this.photos,
this.name,
this.age,
this.education,
this.bio,
this.distance
});
}
class _MainControllerState extends State<MainController> {
static List<Profile> demoProfiles = fetchData();
static fetchData() async{
final db = await Firestore.instance;
List<Profile> list = [];
db.collection("users").getDocuments().then((querySnapshot){
querySnapshot.documents.forEach((document) {
list.add(Profile(
photos: document['photoUrl'],
name: document['photoUrl'],
age: document['photoUrl'],
distance: document['photoUrl'],
education: document['photoUrl']
));
});
});
return list;
}
final MatchEngine matchEngine = MatchEngine (
matches:demoProfiles.map((Profile profile) => Match(profile:
profile)).toList()
);
I am new to flutter.
when I run my code , I got the error :type 'Future' is not a subtype of type 'List .and if I change screen I will get the error:NoSuchMethodError: The method 'map' was called on null. How can I solve it ?
Thank you for helping me .
You need to specify the return type of method fetchData
static Future<List<Profile>> fetchData() async{
You need to convert you method to getData
Future<List<Data>> getData() async {
var response =
await http.get(Uri.https('jsonplaceholder.typicode.com', 'users'));
var jsonData = jsonDecode(response.body);
List<Data> dataList = [];
for (var u in jsonData) {
Data data = Data(u["name"], u["phone"], u["email"]);
dataList.add(data);
}
print(dataList.length);
return dataList;
}
And display in a Card
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Data Fetch"),
),
body: Container(
child: Card(
child: FutureBuilder<List<Data>>(
future: getData(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, i) {
return ListTile(
title: Column(
children: [
Text(snapshot.data![i].name),
Text(snapshot.data![i].phone),
Text(snapshot.data![i].email),
],
),
);
});
}
},
),
),
));
}
Its worked for me :) :) I hope this will help you.