convert Stream<List<String>> to List<String> in flutter - flutter

I am trying to convert a Stream<List<String>> to List<String> in flutter
here is my code
Stream<List<String>> _currentEntries;
/// A stream of entries that should be displayed on the home screen.
Stream<List<String>> get categoryEntries => _currentEntries;
_currentEntries is getting populated with data from a database.
I want to convert _currentEntries into List<String>
I tried the following code but doesn't work:
List<List<String>> categoryList () async {
return await _currentEntries.toList();
}
I get the following error:
A value of type List<List<String>> can't be returned from method categoryList because it has a return type of List<List<String>>
Can someone help how to solve this issues and convert a Stream<List<String> to List<String>?

The issue seems to be with your return type for categoryList. You're returning as List of Lists when the Stream only contains a single layer of List. The return type should be Future<List<String>>.
Use .first, .last, or .single in addition to await to get just a single element, and toList() should be removed.
Future<List<String>> categoryList () async {
return await _currentEntries.first;
}
Also a quick tip: Dart automatically generates getters and setters for all fields so the getter method you show isn't necessary.

As title said, question is how to convert stream of some items to item. So what Christopher answered it is ok but only if you want to take the first value from the stream. As streams are asynchronous, they can provide you a value in any point of a time, you should handle all events from the stream (not only the first one).
Let's say you are watching on a stream from database. You will receive new values from database on each database data modification, and by that you can automatically update GUI according to newly received values. But not if you are taking just first value from stream, it will be updated only the first time.
You can take any value and handle it ("convert it") by using listen() method on a stream. Also you can check this nicely written tutorial on Medium. Cheers!
Stream<List<String>> _currentEntries = watchForSomeStream();
_currentEntries.listen((listOfStrings) {
// From this point you can use listOfStrings as List<String> object
// and do all other business logic you want
for (String myString in listOfStrings) {
print(myString);
}
});

I have no idea that Stream can await for the API call from the server, in my case I'm using BLOC pattern and using Future<List<String>> getCategoryList async () {...} and to get the List I going to use like this:
Future<List<String>> getCategory() async {
var result = await http.get();
//Some format and casting code for the String type here
return result;
}
Hope this help

Related

How do I extract values from a DataSnapshot returned by 'getting' a Firebase database reference in Flutter?

I'm building an app using Flutter and I'm trying to read data from my Firebase Realtime Database. I'm aware of the ability to listen to events on the database, but in this case I need to manually grab data. I'd like to be able to get a snapshot from a database reference as a Map<String, dynamic>, so that I can access my data via keys.
Here's the relevant code I'm using (within a stateful widget):
late Map<String, dynamic> codes;
void getData() async {
var snapshot = await database.ref('codes').get();
codes = snapshot.value;
}
#override
void initState() {
super.initState();
getData();
}
Printing snapshot.value displays a map as expected. Printing snapshot.value.runtimeType displays _InternalLinkedHashMap. But when I attempt to convert that to a Map<String, dynamic> it throws an error because snapshot.value has the type Object? (stated here). I'm also unable to just access data using a key for the same reason.
I tried all of the following with no success. All attempts returned errors relating to the difference in the object types.
// tried converting snapshot value to a Map
var data = snapshot.value as Map<String, dynamic>;
// also tried generating a Map using the 'from' constructor
var data = Map<String, dynamic>.from(snapshot.value);
// tried referencing a value straight from the snapshot value Object
var helpMe = snapshot.value['ABC123'];
What I was expecting was to be able to grab a snapshot and use it as a Map (because that's how the Realtime Database stores information) to access any data within the referenced node. I don't really understand why that wouldn't be exactly how it works, and if not, send me a JSON String I can decode myself.
It's been a long day, and I'm very tired, so I suspect I'm missing something very obvious. How can I convert this to a Map so I can easily access my data? And please don't tell me I'll need to iterate through the children property of the DataSnapshot to get my data, because that sounds unnecessarily painful.
Cheers!

Is it possible to feed a model name into a Future as a variable or use a callback in a Future in Flutter?

Background
I understand the concept of callbacks from JS. Being on day #5 of flutter I don't really understand the interplay with callbacks at all in dart/flutter.
So please bear with me
What I'm trying to accomplish
I'm trying to build a single set of reusable code that can communicate from my app to a JSON site to make various different JSON requests via the same exact code base
What I have right now
I have a Future call which successfully polls a site for JSON data and sends it off for parsing by using a specific model.
Future<Result> getJSONfromTheSite(String thecall) async {
try {
//2
final response = await client.request(requestType: RequestType.GET,
path: thecall).timeout(const Duration(seconds: 8));
if (response.statusCode == 200) {
//3
return Result<MyModel>.success(MyModel.fromRawJson(response.body));
} else {
return Result.error(title:"Error",msg:"Status code not 200", errorcode:1);
}
} catch (error) {
(...)
}
}
I'm making the request via my UI page like this:
FutureBuilder(
future: _apiResponse.getJSONfromTheSite('latestnews'),
builder: (BuildContext context, AsyncSnapshot<Result> snapshot) {
if (snapshot.data is SuccessState) { ...
Using the variable thecall which I feed in from the FutureBuilder on my various UI pages I can successfully change which http JSON request is being made to poll for different data.
What I'm stuck on
Where I'm stuck is that while I can successfully vary the request to change what data I'm polling for, I can't do anything with the result because my current code always wants to use MyModel to parse the JSON.
return Result<MyModel>.success(MyModel.fromRawJson(response.body));
The question
I essentially need to be able to switch the Model being used against this JSON data depending on what UI page is making the request, rather then hardcoding 10 versions of the same code.
I was thinking of feeding in the name of the model I want to use for that specific call via the FutureBuilder call. For example something like future: _apiResponse.getJSONfromTheSite('latestnews', MyModel2), but that didn't work at all.
Alternatively I was thinking of having the entire return Result<MyModel>.success(MyModel2.fromRawJson(response.body)); sent in as a callback.
The concept of callback makes sense from my JS days, but I'm not sure if I'm correctly applying the concept here. So if I'm going about this the wrong way I'm all ears to a more elegant solution.
Thank you!
You could simply pass as a callback the constructor you want to use and make your method getJSONfromTheSite dynamically typed. The only issue is that you won't be able to define fromRawJson as a factory constructor but instead as a static method returning an instance of your object.
Code Sample
// Your method fromRawJson should be implemented like this
class MyModel {
// ...
static MyModel fromRawJson(Map<String, dynamic> json) =>
MyModel(/* your parameters */);
}
/// T is the dynamic type which you will use to define if it is
/// MyModel or any other class.
Future<Result<T>> getJSONfromTheSite<T>(
String thecall,
T Function(Map<String, dynamic>) jsonFactory,
) async {
try {
// ...
return Result<T>.success(jsonFactory(response));
} catch (error) {
// ...
}
}
// Then you can call your method like this
await getJSONfromTheSite<MyModel>('latestnews', MyModel.fromRawJson);
// For another class such as MyModel2 it would be like this
await getJSONfromTheSite<MyModel2>('', MyModel2.fromRawJson);
Try the full code on DartPad

Flutter-Riverpod - how to combine providers to filter on a Stream

I'm trying to follow the example docs on how to combine Providers using Flutter & Riverpod to filter a list of items. The data is coming from Firestore using Streams:
final carListProvider = StreamProvider.autoDispose<List<Car>>((ref) {
final carsRepo = ref.watch(carsRepositoryProvider);
return carsRepo.cars();
});
This all works fine and I can render the list of cars no problem. Now I want to give the user the option to filter the list based on color:
enum CarColorFilter {
all,
red,
white,
black,
}
final carListFilter = StateProvider((_) => CarListFilter.all);
And then following the docs example, my attempt to combine the providers:
final filteredCars = StreamProvider<List<Car>>((ref) {
final filter = ref.watch(carListFilter);
final cars = ref.watch(carListProvider); <-- This line throws the error
switch (filter.state) {
case CarColorFilter.all:
return cars;
case CarColorFilter.red:
return cars.where(...)
default:
}
})
On the line declaring the 'cars' variable the editor complains:
The argument type 'AutoDisposeStreamProvider<List>' can't be
assigned to the parameter type 'AlwaysAliveProviderBase<Object,
dynamic>'
I think the difference between my use case and the docs is that in the example given the List<Todo> is a StateNotifierProvider whereas in my case the List<Car> is a StreamProvider. Any help would be much appreciated.
Found the answer in the docs, posting here in case it helps anyone else:
When using .autoDispose, you may find yourself in a situation where
your application does not compile with an error similar to:
The argument type 'AutoDisposeProvider' can't be assigned to the
parameter type 'AlwaysAliveProviderBase'
Don't worry! This error is voluntary. It happens because you most
likely have a bug:
You tried to listen to a provider marked with .autoDispose in a
provider that is not marked with .autoDispose
Marking the filteredList provider as autoDispose resolves the issue.

Convert Future<List> of custom object in list of object

i need support on a problem.
I have a method of my DatabaseHelper that takes care of recovering users from my database created with SQLite.
So I created a class called DatabaseUserHelper that takes care of retrieving information.
Here is the method of my class:
Future<List> getAllUser() async {
var dbClient = await db;
var result = await dbClient.query(tableUser, columns: [columnId, columnNome, columnCognome, columnUsername]);
return result.toList();
}
My requirement is to convert the result of this method into a normal list.
Here is an example of what I would like to do with Java.
List <User> listUser = getAllUser ()
Unfortunately, however, every time I try to make this conversion I always get an error.
Is there anything I can do ?
Thank you all
You should use await keyword before calling the function getAllUser like below :
List<User> listUser = await getAllUser();
It is because your function has 'Future' keyword which means the result will be available sometime in the future. So, you should wait a bit.

How to save and retrieve a list of custom objects with SharedPreferences?

I'm making a Flutter app for fuel expenses tracking. I have a simple object:
class Entry {
double _fuel;
double _money;
double _distance;
Entry(this._fuel, this._money, this.distance);
Entry.fromJson(Map<String, dynamic> json) => Entry (json['fuel'], json['money'], json['distance']);
Map<String, dynamic> toJson() => {'fuel':_fuel, 'money':_money, 'distance':_distance};
}
Whenever I refill my tank I want to make a new entry and to keep all of them virtually forever. In the app I have a List<Entry> entries, but I couldn't find a way to save that list in SharedPreferences. There is only a method which accepts a list of Strings. Should I create a new List<String> by iterating through my List<Entries> and serializing each entry, and then saving that list to the SharedPreferences or is there an easier way? And when I want to read from SharedPreferences, how can I recreate my list?
Update: Of course, I also need to be able to delete a particular entry from the list.
Thanks.
Saving them as List<String> is a viable option indeed, and the other option I can think of is to serialize the whole list as json using json.encode() method and save it as a string in SharedPreferences, then retrieve and deserialize them and iterate over them to turn them into your custom object again.
SharedPreferences prefs = await SharedPreferences.getInstance();
List<Entry> entries = // insert your objects here ;
final String entriesJson = json.encode(entries.map((entry) => entry.toJson()).toList());
prefs.setString('entries', entriesJson);
final String savedEntriesJson = prefs.getString('entries);
final List<dynamic> entriesDeserialized = json.decode(savedEntriesJson);
List<Entry> deserializedEntries = entriesDeserialized.map((json) => Entry.fromJson(json)).toList();
To remove a particular key (with its value) from SharedPreferences simply use the remove(key) method, or if you are going to use the same key, you can set the key with a new value and it will override the previous value.
To remove a particular item from your List<Entry> you can use either the remove(entry) method of the List class if you can pass the item, or remove an item depending on a certain condition using the removeWhere((entry) => entry.distance > 500) for example.
Hope that helped.
For adding entry you do not need to serialize all entries, just use getStringList of SharedPreferences and add json string of your entry to string list, and save again
var sharedPreferences = await SharedPreferences.getInstance();
List<String> entriesStr = sharedPreferences.getStringList(KEY);
entriesStr.add(jsonEncode(entryToAdd.toJson()));
sharedPreferences.setStringList(KEY, entriesStr);