Slow data loading from firebase causing "RangeError (index)" - flutter

Here i am trying to load image link from firebase into flutter app then passing it into image.network(). the data loading is taking time to load data so it is giving me following error: RangeError (index): Invalid value: Valid value range is empty: 1 but after 5 second the data is loaded successfully and printed in console but not displayed on screen. here is code to print data
class _DataFetching extends State<MyHomePage> {
List<eyes> allCriminals=eyes.allcriminals();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("fetch"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.network(allCriminals[1].link
),
]// This trailing comma makes auto-formatting nicer for build methods.
)));
}
}
and function to load data from firebase is
static List<eyes> allcriminals() {
var allEyes = new List<eyes>();
Future<Query> queryUsers() async{
return await FirebaseDatabase.instance
.reference()
.child('Facial Parts/Eyes');
}
queryUsers().then((query) {
query.once().then((snapshot) {
var result = snapshot.value.values as Iterable;
for (var item in result) {
print(result);
allEyes.add(new eyes(criminal_id: item["criminal_id"],
eye_size: item["eyesize"],
link: item["link"]));
print(item['link']);
}
});
});
return allEyes;
}
is there any method that can help me to load all data and after that it should go to Widget build()?

Data is loaded from Firebase asynchronously. By the time your return allEyes runs, the .then() from the database hasn't run yet. And you can't return something now that hasn't been loaded yet.
That's why you'll have to return a Future<List<eyes>>, which is a promise to have a list of eyes at some point in the future.
static Future<List<eyes>> allcriminals() {
var query = FirebaseDatabase.instance.reference().child('Facial Parts/Eyes');
return query.once().then((snapshot) {
var allEyes = new List<eyes>();
var result = snapshot.value.values as Iterable;
for (var item in result) {
allEyes.add(new eyes(criminal_id: item["criminal_id"],
eye_size: item["eyesize"],
link: item["link"]));
}
return allEyes;
});
}
Then you can use that in your render method by either using a FutureBuilder or by directly calling setState().

Related

How to call graphql query X number of times with different variable values in flutter?

I am using graphql_flutter: ^5.1.0 from pub.dev. Its very easy and helpful. I am able to make queries and mutations easily in it. The only problem is now I am in a situation where I am supposed to call a search query again and again everytime the user enters something in search box. The way I am trying is I have created a separate query method for query widget. In that I am passing different variable value. But it doesnt work. And it gives me same result as before. I am just not sure how I make fresh query call with fresh variable values again and again.
My current code:
#override
Widget build(BuildContext context) {
nameSearch = NameSearch(edgesList, (String s) {
print("searched letter $s");
if (s != null && !s.isEmpty) {
allFilmsQuery(value: ++count);
}
});
return Container(
child: GraphQLProvider(
client: GraphqlConfig.initializeClient(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
final result = await showSearch<Edges>(
context: context,
delegate: nameSearch,
);
print("searched result $result");
},
child: Text("Search")),
allPeopleQuery()
],
)),
);
}
Query method that I have created:
Query allPeopleQuery({int value = 1}) {
var map = MyQueries.getMap(value);
print("allFilmsQuery called:$map");
return Query(
options: QueryOptions(
fetchPolicy: FetchPolicy.networkOnly,
document: gql(MyQueries.allPeople),
// this is the query string you just created
variables: map,
pollInterval: const Duration(seconds: 10),
),
builder: (QueryResult result,
{VoidCallback? refetch, FetchMore? fetchMore}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return const Center(child: CupertinoActivityIndicator());
}
var response = result.data;
if (response != null) {
print("allpeople result:$count ${result.data}");
AllPeopleResponse allPeopleResponse = AllPeopleResponse.fromJson(
response["allPeople"]);
edgesList = allPeopleResponse.edges!;
if (nameSearch != null) {
nameSearch.updateList(edgesList);
}
return Center(child: Text(result.data.toString()));
}
return const Center(child: CupertinoActivityIndicator());
},
);
}
As you can see the query method is being called once in beginning in build method. And it works perfectly fine. And gives me the result that I need the first time. But on recalling it on every search letter entered its not working. And keeps giving the same result it returned the first time. Can anyone help me on this, how can I make repeated graphql query calls with fresh variable values and get fresh/update results?

ValueListenableBuilder is not rebuilding the screen, when hotreloading, it is working

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 [];
}
}

Flutter: Image.memory() fails with bytes != null': is not true -- when there IS data

I'm trying to display an image based on (base64) data coming from a backend, but I keep getting the error bytes != null': is not true.
Here's my code:
class _FuncState extends State<Func> {
Uint8List userIconData;
void initState() {
super.initState();
updateUI();
}
void updateUI() async {
await getUserIconData(1, 2, 3).then((value) => userIconData = value);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
Container(
child: CircleAvatar(
backgroundImage: Image.memory(userIconData).image, // <--- problem here
maxRadius: 20,
),
),
),
);
}
}
Helper code:
Future<Uint8List> getUserIconData(
role,
id,
session,
) async {
var url = Uri.https(kMobileAppAPIURL, kMobileAppAPIFolder);
var response = await http.post(url, body: {
'method': 'getUserProfilePic',
'a': id.toString(),
'b': role.toString(),
'c': session.toString(),
});
if (response.statusCode == 200) {
Map data = jsonDecode(response.body);
return base64Decode(data['img']);
}
return null;
}
I have stepped through the code with a debugger and I have confirmed the helper function is returning the correct series of bytes for the image.
I'd appreciate any pointers.
Further note. The error also says:
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub
This is quite simple; if you take a look at your code you should be able to follow through this sequence of operations.
The widget is created. No action. At this point userIconData is null.
initState is called. async http call is initiated. userIconData == null
build is called. build occurs, throws error. userIconData == null
http call returns. userIconData is set. userIconData == your image
Due to not calling setState, your build function won't run again. If you did, this would happen (but you'd still have had the exception earlier).
build is called. userIconData is set. userIconData == your image
The key here is understanding that asynchronous calls (anything that returns a future and optionally uses async and await) do not return immediately, but rather at some later point, and that you can't rely on them having set what you need in the meantime. If you had previously tried doing this with an image loaded from disk and it worked, that's only because flutter does some tricks that are only possible because loading from disk is synchronous.
Here are two options for how you can write your code instead.
class _FuncState extends State<Func> {
Uint8List? userIconData;
// if you're using any data from the `func` widget, use this instead
// of initState in case the widget changes.
// You could also check the old vs new and if there has been no change
// that would need a reload, not do the reload.
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
updateUI();
}
void updateUI() async {
await getUserIconData(widget.role, widget.id, widget.session).then((value){
// this ensures that a rebuild happens
setState(() => userIconData = value);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
// this only uses your circle avatar if the image is loaded, otherwise
// show a loading indicator.
child: userIconData != null ? CircleAvatar(
backgroundImage: Image.memory(userIconData!).image,
maxRadius: 20,
) : CircularProgressIndicator(),
),
),
);
}
}
Another way to do the same thing is to use a FutureBuilder.
class _FuncState extends State<Func> {
// using late isn't entirely safe, but we can trust
// flutter to always call didUpdateWidget before
// build so this will work.
late Future<Uint8List> userIconDataFuture;
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
userIconDataFuture =
getUserIconData(widget.role, widget.id, widget.session);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
child: FutureBuilder(
future: userIconDataFuture,
builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
if (snapshot.hasData) {
return CircleAvatar(
backgroundImage: Image.memory(snapshot.data!).image,
maxRadius: 20);
} else {
return CircularProgressIndicator();
}
},
),
),
),
);
}
}
Note that the loading indicator is just one option; I'd actually recommend having a hard-coded default for your avatar (i.e. a grey 'user' image) that gets switched out when the image is loaded.
Note that I've used null-safe code here as that will make this answer have better longevity, but to switch back to non-null-safe code you can just remove the extraneous ?, ! and late in the code.
The error message is pretty clear to me. userIconData is null when you pass it to the Image.memory constructor.
Either use FutureBuilder or a condition to check if userIconData is null before rendering image, and manually show a loading indicator if it is, or something along these lines. Also you'd need to actually set the state to trigger a re-render. I'd go with the former, though.

Flutter switch between local data and remote data

In a Flutter app the user must be able to see the retrieved data from an API also when there is no network, e.g. in a house basement. I'm able to retrieve the data from the Api and to store it in a local sqflite db. I'm also able to check whether there is network or not. But how do I implement the flow in order to show local data or remote data? Is it possible to do it in the same screen or do I need two screens? Any help is appreciated.
EDIT
Thank you for all the answers, but I don't check where to fill in the getData() method now. I post what I've done so far:
class WorkAtPop extends StatefulWidget {
#override
_WorkAtPop createState() => _WorkAtPop();
}
class _WorkAtPop extends State<WorkAtPop> {
final String title = 'Work#Pop';
final bgcolor = HexToColor('#ffffff');
final list = List();
final isLoading = false;
List<DropdownChoices> workatpopdropdownchoices = <DropdownChoices>[
DropdownChoices(title: 'Refresh', action: 'refresh', route: '/workatpop'),
];
bool _isVpnEnabled = false;
bool _isLoading = true;
void checkVpn() async {
var isEnabled = await ApiService().isVpnEnabled();
setState(() => _isVpnEnabled = isEnabled);
setState(() => _isLoading = false);
}
#override
void initState() {
super.initState();
checkVpn();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: BaseAppBar(title: title, appBar: AppBar(), dropdownChoices: workatpopdropdownchoices),
backgroundColor: bgcolor,
body: new Container(
padding: const EdgeInsets.all(30.0),
color: bgcolor,
child: (_isLoading)
? new Center(
child: new CircularProgressIndicator(),
)
: new Container(
child: new Text('here my data displayed as list, vpn=$_isVpnEnabled'),
)
),
);
}
}
Now if VPN is enabled I retrieve the data from the api else from the local db. The API is storing the data into the db each time it is called.
Since you're handling everything, it will be easy for you to do so. It's possible only in a single screen.
List<PodoClass> data;
if(internet()) {
data = fecthDataFromNetwork(); // load fron internet
} else {
data = loadLocalData(); // load from database
}
processData(data); // process your data
I hope you got some idea.
If internet is available get data from server and store it in localdb, and if internet is not available get already stored data from localdb.
List<PodoClass> data;
void feachdata() async{
if(isInternet()) {
data = await DataFromNetwork(); // load from internet
storetolocaldb(data); // store to local for later use
} else {
data = getLocalData(); // load from database
}
displaydata(data); // process your data
}
hope it helps..
you can store retrieved data from an API to a local db and can fetch it on both cases offline/online.. Also you can update the db if any new data available in the api
void getData() {
getStoredDataFromDb().then((data){
if(data !=null){
populateUI();
checkForAnyUpdate();
}
else {
if(hasInternetconnection)
getDatafromApi();
}
else{
loadMockOrOldData();}
};
getDatafromApi().then((response){
insertDataIntoTheDatabase(response);
};
}

Dart - processing RxCommand result before send it to RxLoader

I'm writing a Flutter app and I decided to use RxDart to pass my data and events along the managers, services and UI.
Basically I have a service which fetches data from a web service and returns it. Let's assume it returns a List of a model called ExploreEntity.
class ExploreMockService extends ExploreServiceStruct {
final String response = /** a sample json **/;
#override
Future<List<ExploreEntity>> loadExploreData(PaginationInput input) async {
await Future.delayed(new Duration(seconds: 2));
return List<ExploreEntity>.from(jsonDecode(response));
}
}
Now in my manager class I call the loadExploreData method inside a RxCommand.
class ExploreManagerImplementation extends ExploreManager {
#override
RxCommand<void, List<ExploreEntity>> loadExploreDataCommand;
ExploreManagerImplementation() {
loadExploreDataCommand = RxCommand.createAsync<PaginationInput, List<ExploreEntity>>((input) =>
sl //Forget about this part
.get<ExploreServiceStruct>() //and this part if you couldn't understand it
.loadExploreData(input));
}
}
And finally I get the result by a RxLoader and pass it to a GridView if data was fetched successfully.
class ExplorePageState extends State<ExplorePage>{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Explore"),
),
body: Column(children: <Widget>[
Expanded(
child: RxLoader<List<ExploreEntity>>(
commandResults:
sl.get<ExploreManager>().loadExploreDataCommand.results,
dataBuilder: (context, data) => ExploreGridView(data),
placeHolderBuilder: (context) => Center(
child: Center(
child: CircularProgressIndicator(),
),
),
errorBuilder: (context, error) => Center(
child: Text("Error"),
)),
)
]));
}
}
It works like a charm but when I wanted to load the data of the next page from web service and append it to the list, I couldn't find a solution to store the content of previous pages and just append the new page's contents to them, since data is passed along the RxCommand and RxLoader automatically.
When loadExploreData sends the reponse to the manager, I need to firstly append the result to a list, and then send that list as the result to RxLoader. Any suggestions?
Hmm that's a good question if this can be done just using Rx. What I would do is keeping a list of the received items in the manager. So when triggering the command to get the next page the command would first add the new data to the list and then push the whole list to the UI.
I"m curious if there is another solution.
My described approach in a rough code sample
class ExploreManagerImplementation extends ExploreManager {
List<ExploreEntity>> receivedData = <ExploreEntity>[];
#override
RxCommand<void, List<ExploreEntity>> loadExploreDataCommand;
ExploreManagerImplementation() {
loadExploreDataCommand = RxCommand.createAsync<PaginationInput, List<ExploreEntity>>((input)
async {
var newData = await sl //Forget about this part
.get<ExploreServiceStruct>() //and this part if you couldn't understand it
.loadExploreData(input);
receivedData.addAll(newData);
return receivedData;
};
}
}