I'm trying to retrieve an ItemModel class from my repository using streams implementing the BLoC logic in Flutter.
The problem is that the ItemModel is found but not retrieved . Therefore there is another loop triggered and tries to return a different Object, which is not retrieve on the Widgets.
Sample output:
I/flutter ( 4520): trying to get 25314126 from cache
I/flutter ( 4520): 25314126 in transformer
I/flutter ( 4520): 25314126 found !! returning: {id: 25314126, type: story, by: xucheng, time: 1607172267, text: , parent: null, kids: [25314805,25314781,25314684,25314664], dead: 0, deleted: 0, url: https://bitbashing.io/std-visit.html, score: 24, title: std::visit is everything wrong with modern C++ (2017), descendants: 4}
I/flutter ( 4520): ----------item to find: 25314126 and found: Instance of 'ItemModel'
I/flutter ( 4520): returned item Instance of 'ItemModel'
Here the correct Object is found and retrieved but not received on the UI and therefore there is another search right away:
I/flutter ( 4520): trying to get 25314805 from cache
I/flutter ( 4520): 25314805 found !! returning: {id: 25314805, type: comment, by: vasama, time: 1607178963, text: We got here because adding library features is a lot easier and less risky than adding language features. Work on pattern matching [1] is ongoing, but the bar for entry for a language feature is much higher and as such takes a longer time.<p>1. http://wg21.link/p1371, parent: 25314126, kids: [], dead: 0, deleted: 0, url: null, score: null, title: null, descendants: 0}
I/flutter ( 4520): ----------item to find: 25314805 and found: Instance of 'ItemModel'
I/flutter ( 4520): returned item Instance of 'ItemModel'
Which is not the Object I'm trying to retrieve!!!
There is always 'Loading 1' String shown
Here is my main Widget
class NewsDetail extends StatelessWidget{
final int itemId;
NewsDetail({ this.itemId}) ;
#override
Widget build(BuildContext context) {
final bloc = CommentsProvider.of(context);
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('Detail'),
),
body: buildBody(bloc),
);
}
Widget buildBody(CommentsBloc bloc){
return StreamBuilder(
stream: bloc.itemWithComments,
builder:
(context, AsyncSnapshot<Map<int,Future<ItemModel>>> snapshot){
if (!snapshot.hasData){
return Text('Loading 1');
}
final itemFuture = snapshot.data[itemId];
return FutureBuilder(
future: itemFuture,
builder: (context,AsyncSnapshot<ItemModel> itemSnapshot){
if(!itemSnapshot.hasData){
return Text('Loading 2');
}
return buildTitle(itemSnapshot.data);
}
);
},);
}
which indicates that is something wrong with the stream: bloc.itemWithComments,
Here is my BLOC class:
class CommentsBloc{
final _repository = Repository();
final _commentsFetcher = PublishSubject<int>();
final _commentsOutput = BehaviorSubject<Map<int,Future<ItemModel>>>();
//Streams
get itemWithComments => _commentsOutput.stream;
//Sink
Function(int) get fetchItemWithComments => _commentsFetcher.sink.add;
CommentsBloc(){
_commentsFetcher.stream.transform(_commentsTransformer()).pipe(_commentsOutput);
}
_commentsTransformer (){
return ScanStreamTransformer(
(cache,int id,index){
cache[id] = _repository.fetchItem(id);
print('$id in transformer');
cache[id].then((ItemModel item){
item.kids.forEach((kidId)=>fetchItemWithComments(kidId));
});
},
<int,Future<ItemModel>>{},
);
}
dispose(){
_commentsFetcher.close();
_commentsOutput.close();
}
}
And here is how i fetch items in my Repository class
List<Source> sources = <Source>[
newsDbProvider,
NewsApiProvider(),
];
List<Cache> caches = <Cache>[
newsDbProvider,
];
Future<ItemModel> fetchItem(int id) async{
ItemModel item;
var source;
for(source in sources) {
item = await source.fetchItem(id);
print('----------item to find: $id and found: ${item}');
if(item!=null){
break;
}
}
for(var cache in caches) {
if(cache!=source) {
cache.addItem(item);
}
}
print('returned item ${item}');
return item;
}
Even though there is an Instance returned every time why is the snapshot.hasData false?
Also please note that the widget gets invoked just once with the correct id by using onTap method. What have i completely missed?
Even though i used the fetchItemWithComments to sink the items, I still had to add a return statement in the ScanStreamTransformer
_commentsTransformer (){
return ScanStreamTransformer<int,Map<int,Future<ItemModel>>>(
(Map<int,Future<ItemModel>>cache,int id,index){
print(index);
cache[id] = _repository.fetchItem(id);
cache[id].then((ItemModel item){
item.kids.forEach( (kidId) => fetchItemWithComments(kidId) );
});
return cache; //was missing this
},
<int,Future<ItemModel>>{},
);
}
This had been bugging me for days, I went over everything and couldn't find anything so if you are using a ScanStreamTransformer class
Always have a return statement
Related
I'm making favorite list which contains the user favorite journeys using provider then I will display these journeys in the favorite journeys screen.
Favorite.dart:
import 'package:flutter/material.dart';
class Favorite extends ChangeNotifier {
final Text date;
final Text time;
final Text source;
final Text destination;
final Text price;
Favorite(this.date, this.time, this.source, this.destination, this.price);
}
class Following extends ChangeNotifier {
List<Favorite> list = [];
add(favorite) {
list.add(favorite);
notifyListeners();
}
remove(favorite) {
list.remove(favorite);
notifyListeners();
}
}
journeys.dart (Which shows all journeys):
FirebaseAnimatedList(
shrinkWrap: true,
query: Consts.journeyRef.child("journeys"),
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation animation, int index) {
try {
return Consumer<Following>(
builder:
(BuildContext context, value, child) {
return Dismissible(
key: UniqueKey(),
secondaryBackground: buildSwipeActionRight(),
background: buildSwipeActionLeft(),
child: ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
eachTile(
following,
Favorite(
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathDateJourney]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathTimeJourney]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathSourceCity]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathDestinationCity]),
Text(Map<String, dynamic>.from(
snapshot.value as Map)[Consts.pathPriceJourney]),),)
]),
onDismissed: (direction) =>
dismissItem(context, index, direction),
);
},
);
} catch (e) {
customSnackBar(context, e.toString(), 3, Colors.white24, Colors.brown, 17);
return const Text(
"We Can't show you information disabled by the Administrator");
}
}),
eachTile.dart:
ListTile eachTile(following, favorite) {
return ListTile(
leading: Column(
children: [
favorite.date,
const SizedBox(
height: 10,
),
favorite.time,
],
),
title: Row(
children: [
favorite.source,
const SizedBox(
width: 50,
),
favorite.price
],
),
subtitle: favorite.destination,
trailing: IconButton(
icon: following.list.contains(favorite)
? const Icon(Icons.favorite)
: const Icon(Icons.favorite_border_outlined),
onPressed: () {
print(following.list.contains(favorite));
// this print statement always false
if (following.list.contains(favorite)) {
following.remove(favorite);
} else {
following.add(favorite);
}
print(following.list.contains(favorite));
// this print statement always true
print(following.list);
// this print statement print the list and in each time the code execute new instance added to the list
},
),
);
}
This code is working fine as adding the journey to the list but the Problem is that when you click on the favorite icon again the condition
following.list.contains(favorite)
is returning false (Which means this object is not in the list but that's wrong I try to print the list and there is an instance) it seem that the following instance changed but I didn't create any new instance I think it is creating new different instance in each time.
What is the best way to add and remove items to the favorite list using provider?
the output:
I/flutter ( 578): false
I/flutter ( 578): true
I/flutter ( 578): [Instance of 'Favorite']
V/AutofillManager( 578): requestHideFillUi(null): anchor = null
I/flutter ( 578): false
I/flutter ( 578): true
I/flutter ( 578): [Instance of 'Favorite', Instance of 'Favorite']
V/AutofillManager( 578): requestHideFillUi(null): anchor = null
I/flutter ( 578): false
I/flutter ( 578): true
I/flutter ( 578): [Instance of 'Favorite', Instance of 'Favorite', Instance of 'Favorite']
first clean the code here in journey.dart you are using both the methods of provider you can archive this task by
1.you can use consumer widget or provider.of(context) but you are using both ways to call only Following provider
2.in journey.dart if you decided to use consumer then in (BuildContext context, Following value, Widget? child) you can use value and child directly no need to write data types in front eg.. (BuildContext context, value, child)
3.can you display your console output
The problem was that I'm making equal-to operator between two object and it's returning false that is because all objects in the dart language except the primitive data types like string, int, and double... are not equal to each other since no == operator is overridden and set to it, and this include also collections like lists, maps...
the solution is to override the == operator in Favorite class:
class Favorite{
final Text date;
final Text time;
final Text source;
final Text destination;
final Text price;
Favorite(this.date, this.time, this.source, this.destination, this.price);#override
bool operator == (Object other) {
return other is Favorite && date.toString() == other.date.toString() && time.toString() == other.time.toString() && source.toString() == other.source.toString() && destination.toString() == other.destination.toString() && price.toString() == other.price.toString();
}
}
now when I run following.list.contains(favorite) or following.list[0]==favorite in listTile Widget it will return true.
and that's it
Every time you make a change, you should call notifyListeners();. An example Implementation would be:
Inside your Provider Class:
void add(int n) {
myList.add(n);
notifyListeners();
}
I'm trying to build a note app, all data and other things is working perfectly, cos the data is displaying to the screen when the code file is saving, its weird , first time facing this problem
in short, the valuelistanble is not listening when the data adding from app, but when just hot reloading the data is displaying
how can i fix this,
here is the code
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance!.addPostFrameCallback((_) async {
final value = await NoteDB.instance.getAllNotes();
});
____________________________________________
____________________________________________
//code line for aligment
Expanded(
child: ValueListenableBuilder(
valueListenable: NoteDB.instance.noteListNotifier,
builder: (context, List<NoteModel> newNotes, _) {
return GridView.count(
childAspectRatio: 3 / 4,
crossAxisCount: 2,
mainAxisSpacing: 34,
crossAxisSpacing: 30,
padding: const EdgeInsets.all(20),
//generating list for all note
children: List.generate(
newNotes.length,
(index) {
//setting the notelist to a variable called [note]
final note = newNotes[index];
if (note.id == null) {
//if the note's id is null set to sizedbox
//the note id never be null
const SizedBox();
}
return NoteItem(
id: note.id!,
//the ?? is the statement (if null)
content: note.content ?? 'No Content',
title: note.title ?? 'No Title',
);
},
),
);
},
)),
here is the NoteDB.instance.getAllNotes(); function
#override
Future<List<NoteModel>> getAllNotes() async {
final _result = await dio.get(url.baseUrl+url.getAllNotes);
if (_result.data != null) {
final noteResponse = GetAllNotes.fromJson(_result.data);
noteListNotifier.value.clear();
noteListNotifier.value.addAll(noteResponse.data.reversed);
noteListNotifier.notifyListeners();
return noteResponse.data;
} else {
noteListNotifier.value.clear();
return [];
}
}
and also there is a page to create note , and when create note button pressed there is only one function calling here is function
Future<void> saveNote() async {
final title = titleController.text;
final content = contentController.text;
final _newNote = NoteModel.create(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
content: content,
);
final newNote = await NoteDB().createNote(_newNote);
if (newNote != null) {
print('Data Added to the DataBase Succesfully!');
Navigator.of(scaffoldKey.currentContext!).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => HomePage()),
(Route<dynamic> route) => false);
} else {
print('Error caught while data adding to the DataBase');
}
}
everything work fine, but while add the data the UI isn't refreshing even tho notifier is active
and if you need full code please have a look at this github link : https://github.com/Mishalhaneef/Note-app
Since this ValueNotifier has a type of List<NoteModel>, the value will not change when you add new items to the list or delete from it or clear all. The value here is a reference to the list which does not change.
You have to assign a new value to it, like:
noteListNotifier.value = List<NoteModel>[<add your current items here>];
You can manipulate your current list with List.from, removeWhere, add etc., and then re-assign the complete list.
Besides you don't need to call notifyListeners in case of a ValueNotifier, the framework handles it, see here.
Another approach would be to use a custom ChangeNotifierProvider where you can call notifyListeners when the contents of your list are changed.
Some further suggestions:
In your homescreen.dart file, instead of NoteDB.instance.noteListNotifier.value[index] you can use newNotes[index].
In data.dart, within getAllNotes, you have to set a new value for noteListNotifier in order to get the changes propagated. Currently you are just modifying items in this list and that is not considered to be a change. Try this code:
#override
Future<List<NoteModel>> getAllNotes() async {
//patching all data from local server using the url from [Post Man]
final _result = await dio.get(url.baseUrl+url.getAllNotes);
if (_result.data != null) {
//if the result data is not null the rest operation will be operate
//recived data's data decoding to json map
final _resultAsJsonMap = jsonDecode(_result.data);
//and that map converting to dart class and storing to another variable
final getNoteResponse = GetAllNotes.fromJson(_resultAsJsonMap);
noteListNotifier.value = getNoteResponse.data.reversed;
//and returning the class
return getNoteResponse.data;
} else {
noteListNotifier.value = <NoteModel>[];
return [];
}
}
i am using this DbProvider class in many other files. it throws error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception:
LateInitializationError: Field 'Db' has not been initialized.
E/flutter (12867): #0 DbProvider.Db (package:sample/src/Db.dart)
E/flutter (12867): #1 Bloc.fetchInfoWithNameOrder
(package:sample/src/bloc.dart:31:24)
E/flutter (12867): #2 NameTiles.build.
(package:sample/src/NameTiles.dart:20:16)
E/flutter (12867): #3 StreamBuilder.build
(package:flutter/src/widgets/async.dart:546:81)
This is DbProvider class
class DbProvider{
late Database Db;
DbProvider(){init();}
init()async{
print("sdsdfaf");
Directory dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, 'AppDb.db');
print(dir.path);
Db = await openDatabase(
path,
version: 1,
onCreate: (Database newDb, int version){
newDb.execute(
"""
create table Names(
id integer primary key,
name text,
Salary integer,
total integer
);
create table Att(
id integer primary key,
name text,
);
"""
);
}
);
print('\n\n\ndb init\n\n\n');
}
}
This is bloc.dart file
class Bloc{
final db = DbProvider();
final _text_field_subject = BehaviorSubject();
final _tile = BehaviorSubject();
Function(String?) get addToTextSubject => _text_field_subject.sink.add;
Stream get text_field_subject => _text_field_subject.stream;
get text_field_subject_value => _text_field_subject.value;
Function get addToTile => _tile.sink.add;
Stream get tile => _tile.stream;
fetchInfo(String arg) async{
print('\nfetchInfo\n');
var ans = await db.Db.query(
"Names",
where: "name = ?",
whereArgs: [arg],
);
print(ans);
addToTile(ans);
}
fetchInfoWithNameOrder()async{
print('\nfetchinfowithorder\n');
var ans = await db.Db.query(
"Names",
orderBy: "name asc",
);
print(ans);
addToTile(ans);
}
}
and here is the NameTiles class with builder:
class NameTiles extends StatelessWidget{
#override
Widget build(BuildContext context) {
print('\n\n\n1\n\n\n');
final db = DbProvider();
print('\n\n\n3\n\n\n');
final bloc = StreamProvider.of(context);
return StreamBuilder(
stream: bloc.tile,
builder: (context, AsyncSnapshot? snapshot){
print('\n\n\nre\n\n\n');
if(snapshot == null || !snapshot.hasData){
print('\n\n\nStarting\n\n\n');
bloc.fetchInfoWithNameOrder();
return Text('loading');
}
print('\n\n\ntween\n\n\n');
return Text('llll');
},
);
}
Widget tiles(info){
return Container(
height: 40,
padding: EdgeInsets.all(5),
clipBehavior: Clip.none,
child: Row(children: [
Text('${info.name}'),
Text('${info.total}'),
],),
);
}
}
You call init in your constructor, but you never wait for it to finish.
So your constructor runs, it trigers the init method, and while that runs, since it's async and has no await to wait for it, your program continues, finally coming to a part where your variable is needed, but not initialized yet, since init is still running.
Some tips:
crank up warnings to a maximum, maybe using a package like pedantic
actually provide return types for your async methods
move your construction of DbProvider out of your build method
instead of having a constructor, create a factory method and make it async, remember to provide a proper return type and call it with an await.
You may want to read up on What is a Future and how do I use it?
I am getting an error trying to run this code about the method '[]' being null. Is it maybe the list view? The output doesn't even show up in debug console anymore. Sorry I'm new to this.
The code:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class GetStats extends StatefulWidget {
#override
_GetStatsState createState() => _GetStatsState();
}
class _GetStatsState extends State<GetStats> {
Map data;
Future<String> getData() async {
var response = await http.get(
Uri.encodeFull(
'http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=730&key=D5F4E0DED484F47380C2804A529BAEDC&steamid=76561198406742636'),
headers: {"Accept": "application/json"});
setState(() {
data = json.decode(response.body);
});
print(data["playerstats"]["stats"][0]["value"]);
}
#override
void initState() {
getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CSGO STATS'),
centerTitle: true,
),
body: ListView.builder(
itemCount: 20,
itemBuilder: (
BuildContext context,
int index,
) {
return Card(
child: Text(
data["playerstats"],
),
);
}),
);
}
}
Error
I/flutter ( 4037): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4037): The following NoSuchMethodError was thrown building:
I/flutter ( 4037): The method '[]' was called on null.
I/flutter ( 4037): Receiver: null
I/flutter ( 4037): Tried calling: []("playerstats")
I/flutter ( 4037):
I/flutter ( 4037): When the exception was thrown, this was the stack:
What am I doing wrong? Do I need to initialize something?
You are making correct http request and getting data too
However the problem lies in your widget , I checked the API response it is throwing a response of Map<String,dynamic>
While displaying the data you are accessing the data using playerstats key which in turn is giving you a Map which is not a String as required by Text Widget in order to display it !
You can display the data by simply converting it to String by using toString() method like this
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CSGO STATS'),
centerTitle: true,
),
body: ListView.builder(
itemCount: 20,
itemBuilder: (
BuildContext context,
int index,
) {
return Card(
child: Text(
data["playerstats"].toString(),
),
);
}),
);
}
Also, I would like to suggest you some Improvements in your Code
Use type annotation as much as possible, It makes your code more safe and robust , Like you have declared data variable as Map data; , You should declare it like Map<String,dynamic> data;
I think you forget to call super.initState() in your initState() method , Make sure to call it before all your methods.
Method getData doesn't return anything, So make it as Future<void> instead of Future<String>
Since you are new contributor to StackOverflow , I welcome you !
Happy Fluttering !
data["playerstats"] is a Map while the Text widget needs String.
Your method is ok, but the problem is in initiating the text widget.
child: Text(
data["playerstats"],
),
['playerstats'] is not a single text, its a map of list. You need to specify the exact text field name you want to see. Still it will show you full data if you add .toString() with the field name.
I am trying to build a futureBuilder with a Future Function that I have which passes through a list of objects which contain the surveyName parameter, I am trying to display that surveyName descriptor into a listView but am noticing that I am getting a blank white screen that isn't showing both the container which is just supposed to show some basic loading functionality and it isn't displaying the information of the getSurveys function either.
I am new to both dart and flutter, so this may just have some basic simple resolution but any information would be helpful. The method getSurveys below in the print displays both names in the print so I know the information is coming in correct for that specific function but I am wondering why it isn't working within the futureBuilder.
The 1 and 2 print statements are running but I am noticing the 3 print statement isn't so that listView Builder is not being invoked for some starnge reason, which may be the cause of this dilemma but I wonder why the container which is just supposed to showcase a loading... is not working correctly either. Output is below this function.
Future<List<Survey_List>> getSurveys() async{
Map map = {
'Type': "getSurveys"
};
var _list = [];
List<Survey_List> surveys = [];
getPost(map).then((Map value){
_list.addAll(value["survey"]);
for (int i = 0; i < _list.length; i++){
Survey_List survey_list = Survey_List(surveyName: _list[i].toString() ,surveyDescription: "", surveyVersion: "");
surveys.add(survey_list);
print(survey_list.surveyName);
}
});
return surveys;
}
Output is as follows:
I/flutter ( 2020): 2
I/flutter ( 2020): 1
I/flutter ( 2020): {"survey":["Survey","Surve3ww"]}
I/flutter ( 2020): Survey
I/flutter ( 2020): Surve3ww
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter_application/Http.dart';
import 'Config.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class Survey_List extends StatefulWidget {
String surveyName;
String surveyDescription;
String surveyVersion;
Survey_List({
this.surveyName,
this.surveyDescription,
this.surveyVersion,
Key key,
}) : super (key: key);
#override
_SurveyListState createState() => _SurveyListState();
}
class _SurveyListState extends State<Survey_List> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Surveys"),
),
body: new Container(
child: new FutureBuilder <List<Survey_List>>(
future: getSurveys(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<Survey_List> surveys = snapshot.data;
if (snapshot.hasData) {
print(1);
return new ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
print(3);
Survey_List survey_list = surveys[index];
print(survey_list.toString());
return new ListTile(
title: new Text(survey_list.surveyName)
);
},
);
}
else{
print(2);
return new Container(
child: new Center(
child: new Text("Loading...")
)
);
}
},
),
),
);
}
}