How can I save widget's data to device in Flutter? - flutter

when a file add I want to do an easy way to access that file in my app. to do that I am using ListView.builder() widget. but I can't save the data in the app
I worked on the shared_preferences package. At least I don't know how I must do

I solved my problem with this block, I am using ListviewBuilder to create widgets so I needed an array. after this, I wanted to save this data on my device. I am using shared preferences but shared preferences only allow to add string array. then, I decoded all arrays to String array then, when I start the widget in initstate. I take data then, encode the array data then, add value to my variable.
SharedPreferences? sharedPref;
List<Map<String, dynamic>> documents = [];
List<String> documentsEncode = [];
#override
void initState() {
super.initState();
getInstance();
}
Future<void> getInstance() async {
final sharedPref = await SharedPreferences.getInstance();
documentsEncode = sharedPref.getStringList(widget.collection!) ?? [];
documentsEncodeToDecode();
// main();
}
documentsEncodeToDecode() {
documents.clear();
documentsEncode.forEach((documentEncode) {
Map<String, dynamic> document = jsonDecode(documentEncode);
documents.add(document);
});
setState(() {});
}

Related

SharedPrefrences delay before initializing - Flutter

I'm using Shared preferences to save the user's name and login state even after closing the app. the Shared Preference I used in main.dart is fine because I used it in the main function and made it async, but when I'm trying to use it in other classes, I see a dark red screen for less than a second before loading the page and it makes my app so ugly. what can I do to fix it?
Here's my code:
late bool _isEditingText;
TextEditingController _editingController = TextEditingController();
late String initialText ;
SharedPreferences? _prefs;
#override
void initState(){
super.initState();
initializePreference().whenComplete((){
setState(() {});
});
}
Future<void> initializePreference() async{
_prefs = await SharedPreferences.getInstance();
String? name = _prefs?.getString('name');
if (name == null) {
_isEditingText = true;
initialText = 'Enter ur name';
} else {
_isEditingText = false;
initialText = name;
}
}
Update:
sorry for not including my exact error... here it is :
LateInitializationError: Field '_isEditingText#37486951' has not been initialized.
I think you are performing setState before all widgets are get initialised. So for that you can update initState as below:
void initState(){
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) async {
initializePreference().whenComplete((){
setState(() {});
});
});
}
If it's not causing issue, than you have to show loading indicator. Like initially when there is no data indicator will be there and once you get data from SharedPreference in setState - you have to remove indicator and load your data.
You can use CircularProgressIndicator for that.
initialise your boolean variable,
var isDataLoad = false;
once you get data in whenComplete(), set it as true and based on this variable you can declare your widgets.
isDataLoad ? Container( // Your widgets where you use "initialText" ) : CircularProgressIndicator();

Flutter: Realtime database appends list when listens changes

I'm using flutter and firebase realtime database.I'm trying to read data from a specific node.I'm saving the data that I am collecting in the Orderlist class and then I return a Future List of Ordelist.This Future function I am trying to use on another widget.I want to display on screen every time data is updated.
Future<List<Orderlist>> order() async{
String business =await businessname();
List table = await tables();
List<Orderlist> list = [];
table.forEach((element) async{
String payment_method = '';
String payment_state ='';
var snapshot = ref.child(business).child(element.toString()).onValue.listen((event) {
event.snapshot.children.forEach((method) {
if(method.key=='payment_method') payment_method=method.value.toString();
if(method.key=='payment_state') payment_state = method.value.toString();
});
final order = Orderlist(payment_method: payment_method,payment_state: payment_state);
list.add(order);
});
});
return list;
}
The problem is that at first place the data are loaded on screen but when I am trying to update the data for some reason the list is appended whereas I just want to replace the previous data with the updated data.To be more specific if I want to listen to 2 nodes to be updated I will have a list with 2 Orderlist items.But the problem is when I update one of them the list is expanded to 3 Orderlist items.
Here is the widget where I am trying to use the Future function
first data loaded Updated da
class TempSettings extends StatefulWidget {
const TempSettings({super.key});
#override
State<TempSettings> createState() => _TempSettingsState();
}
class _TempSettingsState extends State<TempSettings> {
String? business;
List<Orderlist> list=[];
final user = FirebaseAuth.instance.currentUser;
#override
void initState() {
// TODO: implement initState
g();
super.initState();
}
void g() async{
list = await DatabaseManager(user_uid: user!.uid).order();[![[![enter image description here](https://i.stack.imgur.com/hn2NQ.png)](https://i.stack.imgur.com/hn2NQ.png)](https://i.stack.imgur.com/SJ4M1.png)](https://i.stack.imgur.com/SJ4M1.png)
}
#override
Widget build(BuildContext context) {
return Column(children: list.map((e) => ListTile(title: Text(e.payment_method!),)).toList(),);
}
}
When you listen to data in Firebase with onValue, the event you get contains a snapshot of all data. Even when only one child node was added/changed/removed, the snapshot will contain all data. So when you add the data from the snapshot to list, you are initially adding all child nodes once - but then on an update, you're adding most of them again.
The easiest way to fix this is to empty the list every time onValue fires an event by calling list.clear().

LateInitializationError: Field 'loggedInEmployee' has not been initialized

I'm trying to load in the details of an employee that I saved in storage via SharedPreferences before making any requests to my API to make requests.
This is the last thing I have tried
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
loggedInEmployee =
Employee.fromJson(jsonDecode(prefs.getString("employee")!));
});
_loadDayTemplates();
}
Other things I have tried include making a Future function that returns the Employee object and put that in my initState() function, as well as trying to pass through the employee object from SharedPreferences through from a previous page. None of these solutions have worked so far unfortunately, the page always seems to be loaded in before the employee object is actually present.
Are there any other things I could try to fix this?
initState call before building 1st frame and we have a addPostFrameCallback to load data. But _loadDayTemplates() method is outside the scope of callback. means it will only load on initState and wont refresh on addPostFrameCallback.
To load data and refresh the we need to include _loadDayTemplates() and use setState inside addPostFrameCallback.
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
loggedInEmployee =
Employee.fromJson(jsonDecode(prefs.getString("employee")!));
_loadDayTemplates(); // reload the templates
setState((){}); refresh the UI
});
_loadDayTemplates();
}

Google Maps Marker using Network Image (Unable to interpret bytes as a valid image.)

I'm trying to get my google map markers to generate icons from firebase storage, I've tried a few things with BitmapDescriptor and Uint8list but fail to understand what is needed to make this work. What is needed to make the markers icons from network images? I've seen this plugin but it doesn't work for web. What do I need to do to make the code below show markers as icons.
deleted old bad example code
I've updated the code attempting to implement Curt's suggestions. I finally have it down to one error that seems I'm very close.
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(error, Unable to interpret bytes as a valid image., null, java.lang.IllegalArgumentException: Unable to interpret bytes as a valid image.
When I print the bytes it comes out as a series of numbers, when I print _bitmapDesciptor I unfortunately get "Instance of 'BitmapDescriptor'" which backs up the error message.
What do I need to do to get it to interpret correctly?
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../models/marker_collect_model.dart';
class ShowMapPublic extends StatefulWidget {
#override
_ShowMapState createState() => _ShowMapState();
}
class _ShowMapState extends State<ShowMapPublic> {
GoogleMapController? controller;
List<Marker> list = [];
List<String> listDocuments = [];
void _onMapCreated(GoogleMapController controllerParam) {
setState(() {
controller = controllerParam;
});
}
void readDataFromFirebase() async {
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference<Map<String, dynamic>> collectionReference =
firestore.collection('2022BHAM');
collectionReference.snapshots().listen((event) async {
List<DocumentSnapshot> snapshots = event.docs;
for (var map in snapshots) {
Map<String, dynamic> data =
map.data() as Map<String, dynamic>; // add this line
MarkerCollectModel model =
await MarkerCollectModel.fromMap(data); // use data here
String nameDocument = map.id;
listDocuments.add(nameDocument);
Marker marker = await createMarker(model, nameDocument);
setState(() {
list.add(marker);
});
}
});
}
Future<BitmapDescriptor> _buildMarkerIcon(String pathImage) async {
// Fetch the PNG
Image _image = await Image.network(pathImage);
// Encode the image as a list of ints
List<int> list = utf8.encode(_image.toString());
// Convert the int list into an unsigned 8-bit bytelist
Uint8List bytes = Uint8List.fromList(list);
// And construct the BitmapDescriptor from the bytelist
BitmapDescriptor _bitmapDescriptor = BitmapDescriptor.fromBytes(bytes);
// And return the product
return _bitmapDescriptor;
}
Future<Marker> createMarker(
MarkerCollectModel markerCollectModel, String nameDocument) async {
Marker marker;
marker = Marker(
markerId: MarkerId(nameDocument),
icon: await _buildMarkerIcon(markerCollectModel.urlavatar!),
position: LatLng(markerCollectModel.lat!, markerCollectModel.lng!),
);
return marker;
}
Set<Marker> myMarkers() {
return list.toSet();
}
// Method
#override
void initState() {
super.initState();
readDataFromFirebase();
}
#override
Widget build(BuildContext context) {
CameraPosition cameraPosition =
CameraPosition(target: LatLng(40.788717, -99.496509), zoom: 2.5);
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
GoogleMap(
initialCameraPosition: cameraPosition,
markers: myMarkers(),
onMapCreated: _onMapCreated,
),
],
)));
}
}
The icon is supposed to be BitmapDescriptor class. The class can only be initialized with a json, through the BitmapDescriptor.fromJson() constructor. The json is supposed to describe the color of the marker, mostly the color. See the official docs here.
If you have to use an image as an icon, you should use the flutter_map package. It would mean you will have to redo a lot of your code though. This tutorial might help.
First know that this answer should be read as a solution outline. I didn't implement any of this in actual working code. That being said, it should work. I've put this solution together based on reading the various documentations, and this is what I would try if I were actually going to code it.
Constraint: Per Google documentation, the image being loaded must be encoded (or at least returned) as a PNG.
Solution Plan
BitmapDescriptor provides a FromBytes method that can be used to construct a BitMapDescriptor from a serialized data stream.
Next, observe that Image provides a toString() method that will convert your loaded image into a string.
Then you can convert that string into a Uint8List by leveraging a couple of utility methods.
List<int> list = utf8.encode(source);
Uint8List bytes = Uint8List.fromList(list);
(Shout out to Osama Gamal for providing this part of the solution in a previous answer)
And finally use BitmapDescriptor.FromBytes() to produce the icon data.
Again, assuming that the network image that you load is a PNG as required by BitMapDescriptor, it seems possible to reach your target.
Putting it all together...
I have written this as an async method. There are other ways to
wait for the image to come in, but this is easiest for illustration purposes.
You will 'await _buildMarkerIcon(foo)' or '_buildMarkerIcon(foo).then((Bitmap Descriptor bmd) { use it }' as your use case requires.
Good luck and I hope this helps someone.
BitmapDescriptor _buildMarkerIcon(String pathImage) async {
// Fetch the PNG
Image _image = await Image.network(pathImage);
// Encode the image as a list of ints
List<int> list = utf8.encode(_image.toString());
// Convert the int list into an unsigned 8-bit bytelist
Uint8List bytes = Uint8List.fromList(list);
// And construct the BitmapDescriptor from the bytelist
BitmapDescriptor _bitmapDescriptor = BitmapDescriptor.fromBytes(bytes);
// And return the product
return _bitmapDescriptor;
}

How to retain data while using ScopedModel in Flutter?

I am using ScopedModel to fetch some data from a Firebase database.
I am fetching a list of events.
I fetch the events from the endpoint inside the Model;
I store the events into a List<Event> inside the model;
I use that list to build my ListView.
mixin EventModel on Model {
List<Event> _events = [];
Future<http.Response> fetchEvents() async {
http.Response response = await http.get(//Url);
final List<Event> fetchedEvents = [];
... // decode response data into fetchedEvents
// Add the loaded data to my List
_events = fetchedEvents;
notifyListeners();
...
}
}
So, when opening the EventsPage the first thing I do is to fetch the data in initState().
class _EventPageState extends State<EventPage> {
#override
void initState() {
super.initState();
widget.model.fetchEvents();
}
}
}
After fetching the network data, my List inside my app has the network data so I can use it to build my ListView.
EventsPage.dart
Widget _buildListView(MainModel model) {
return Center(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ItemEventBig(model.events[index], index);
},
itemCount: model.events.length,
),
);
}
My problem is that, if I navigate to another page and then come back to EventsPage, initState() will be called again, so fetchEvents. Making the app reload all the events list again.
I would like to retain the downloaded data while my app is alive, so If the user go and come back to EventsPage the data will not be lost.
I was used to do it in Android using ViewModel, how to do it in Flutter?
I want to keep using ScopedModel to do my State Management in Flutter.
Possible Solution
I thought that a solution would be to store the events in a List<Event> as I am doing. Then, when calling fetchEvents() I could first check if my List<Event> is not null if so, I don't need to call it again because data was already loaded.
This seems a bad solution for me, especially when I have multiple pages fetching the data. Suppose I load the first one, when I go to the second one it will assume the data was already loaded because List<Event> is non null and it will not load again.
See Flutter Documentation - https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html
class _EventPageState extends State<EventPage>
with AutomaticKeepAliveClientMixin<EventPage> {
#override
void initState() {
super.initState();
widget.model.fetchEvents();
}
}
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}