Flutter FireStore empty list - list

I'm trying to read the ID of each doc in a collection. As I can saw, doc.id is a string, and it has the value name for each doc. So I just tried to add that value to a list, for later pass it to a DropDownButton. But for some reason, the list return null.
List<String> readthishit() {
List<String> ex;
FirebaseFirestore.instance
.collection('Enrollment')
.get()
.then((QuerySnapshot querySnapshot) => {
querySnapshot.docs.forEach((doc) {
ex.add(doc.id);
})
});
return ex;
}
What's happening?

You need to use the await keyword & wait for the result from Firebase.
Currently, you are sending a call to the Firebase but, before the result from Firebase, your code is returning the null list ex.
Future<List<String>> readThisShit() async {
List<String> ex = <String>[];
final querySnapshot = await FirebaseFirestore.instance
.collection('Enrollment')
.get();
querySnapshot.docs.forEach((doc) {
ex.add(doc.id);
});
return ex;
}
Also, I think you should use lowerCamelCase notation for your method names. So, readthisshit will become readThisShit.

Related

Dart Testing with Riverpod StateNotifierProvider and AsyncValue as state

This is my first app with Dart/Flutter/Riverpod, so any advice or comment about the code is welcome.
I'm using Hive as embedded db so the initial value for the provider's state is loaded asynchronously and using an AsyncValue of riverpod to wrapped it.
The following code works but I've got some doubts about the testing approach, so I would like to confirm if I'm using the Riverpod lib as It supposed to be used.
This is my provider with its deps (Preferences is a HiveObject to store app general config data):
final hiveProvider = FutureProvider<HiveInterface>((ref) async {
return await App.setUp();
});
final prefBoxProvider = FutureProvider<Box<Preferences>>((ref) async {
final HiveInterface hive = await ref.read(hiveProvider.future);
return hive.openBox<Preferences>("preferences");
});
class PreferencesNotifier extends StateNotifier<AsyncValue<Preferences>> {
late Box<Preferences> prefBox;
PreferencesNotifier(Future<Box<Preferences>> prefBoxFuture): super(const AsyncValue.loading()) {
prefBoxFuture.then((value) {
prefBox = value;
_loadCurrentPreferences();
});
}
void _loadCurrentPreferences() {
Preferences pref = prefBox.get(0) ?? Preferences();
state = AsyncValue.data(pref);
}
Future<void> save(Preferences prefs) async {
await prefBox.put(0, prefs);
state = AsyncValue.data(prefs);
}
Preferences? get preferences {
return state.when(data: (value) => value,
error: (_, __) => null,
loading: () => null);
}
}
final preferencesProvider = StateNotifierProvider<PreferencesNotifier, AsyncValue<Preferences>>((ref) {
return PreferencesNotifier(ref.read(prefBoxProvider.future));
});
And the following is the test case, I'm mocking the Hive box provider (prefBoxProvider):
class Listener extends Mock {
void call(dynamic previous, dynamic value);
}
Future<Box<Preferences>> prefBoxTesting() async {
final hive = await App.setUp();
Box<Preferences> box = await hive.openBox<Preferences>("testing_preferences");
await box.clear();
return box;
}
void main() {
test('Preferences value changes', () async {
final container = ProviderContainer(overrides: [
prefBoxProvider.overrideWithValue(AsyncValue.data(await prefBoxTesting()))
],);
addTearDown(() {
container.dispose();
Hive.deleteBoxFromDisk("testing_preferences");
});
final listener = Listener();
container.listen<AsyncValue<Preferences>>(
preferencesProvider,
listener,
fireImmediately: true,
);
verify(listener(null, const TypeMatcher<AsyncLoading>())).called(1);
verifyNoMoreInteractions(listener);
// Next line waits until we have a value for preferences attribute
await container.read(preferencesProvider.notifier).stream.first;
verify(listener(const TypeMatcher<AsyncLoading>(), const TypeMatcher<AsyncData>())).called(1);
Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
preferences.currentListName = 'Lista1';
await container.read(preferencesProvider.notifier).save(preferences);
verify(listener(const TypeMatcher<AsyncData>(), const TypeMatcher<AsyncData>())).called(1);
verifyNoMoreInteractions(listener);
final name = container.read(preferencesProvider.notifier).preferences!.currentListName;
expect(name, equals('Lista1'));
});
}
I've used as reference the official docs about testing Riverpod and the GitHub issue related with AsyncValues
Well, I found some problems to verify that the listener is called with the proper values, I used the TypeMatcher just to verify that the state instance has got the proper type and I check ("manually") the value of the wrapped object's attribute if It's the expected one. Is there a better way to achieve this ?
Finally, I didn't find too many examples with StateNotifier and AsyncValue as state type, Is there a better approach to implement providers that are initialized with deferred data ?
I didn't like too much my original approach so I created my own Matcher to compare wrapped values in AsyncValue instances:
class IsWrappedValueEquals extends Matcher {
final dynamic value;
IsWrappedValueEquals(this.value);
#override
bool matches(covariant AsyncValue actual, Map<dynamic, dynamic> matchState) =>
equals(actual.value).matches(value, matchState);
#override
Description describe(Description description) => description.add('Is wrapped value equals');
}
In the test, the final part is a bit different:
Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
preferences.currentListName = 'Lista1';
await container.read(preferencesProvider.notifier).save(preferences);
// the following line is the new one
verify(listener(IsWrappedValueEquals(Preferences()), IsWrappedValueEquals(preferences))).called(1);
verifyNoMoreInteractions(listener);
}
I prefer my custom Matcher to the original code, but I feel that there are too many custom code to test something, apparently, common.
If anyone can tell me a better solution for this case, It'd be great.

delete one element in a list in sharedPreferences

I'm trying to delete an element in my favoriteList here but this doesn't seem to be working. I've looked on the web but couldn't find anything related to this. They were all about how to clear sharedPreferences or delete a key.
Future<void> removeFav(String articleId) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
favoriteList = prefs.getStringList('favoriteList');
if (favoriteList != null) {
await prefs.remove('${favoriteList!.where((id) => id == articleId)}'); //I'm guessing id here returns an element of this list..??
print('unfavorited');
setState(() {
isFavorite = false;
});
} else {
print('favoriteList was null');
}
}
You need to first remove the item from the list:
SharedPreferences prefs = await SharedPreferences.getInstance();
// get the list, if not found, return empty list.
var favoriteList = prefs.getStringList('favoriteList')?? [];
// remove by articleId
favoriteList.removeWhere((item) => item == articleId);
Then, saved the changed favoriteList back to sharedPreferences:
prefs.setStringList('favoriteList', favoriteList);
You should do these steps to save List of object in SharedPreferences:
convert your object to map → toMap() method
encode your map to string → encode(...) method
save the string to shared preferences
for restoring your object:
decode shared preference string to a map → decode(...) method
use fromJson() method to get your object
So in this case I think you should restore the list from shared preference and modify list and then save new list in shared preference.

Truffle uint test script not reflecting updated storage variable, and strange behaviour

uint256 public burningRatePercent = 50;
function onlyOwnerSetBurningRate(uint256 _burningRatePercent) onlyOwner public returns (uint)
{
burningRatePercent = _burningRatePercent;
return (burningRatePercent);
}
//and the test script is
it("should return set value from onlyOwnerSetBurningRate", function() {
var token;
return Token.deployed().then(function(instance){
token = instance;
const tst = token.onlyOwnerSetBurningRate.call(1234);
return tst;
}).then(function(result){
assert.equal(result.toNumber(), 1234, 'onlyOwnerSetBurningRate failed');
var ret = token.burningRatePercent.call();
return ret;
}).then(function(result){
assert.equal(result.toNumber(), 1234, 'Reading BurningRate set value failed');
});
});
//and this code below also produces same problem
it("should return set value from onlyOwnerSetBurningRate", async function() {
var token;
token = await Token.deployed();
const tst = await token.onlyOwnerSetBurningRate.call(1234);
assert.equal(tst.toNumber(), 1234, 'onlyOwnerSetBurningRate failed');
const tst2 = await token.burningRatePercent();
assert.equal(tst2.toNumber(), 1233, 'onlyOwnerSetBurningRate failed');
});
even after updating public variable 'burningRatePercent' it shows old value
while testing I got a strange behavior of truffle, please see the subject smart contract code first
My question is straight forward if return of 'tst' is '1234' then why return of 'ret' is 50 ?
in the test code burningRatePercent should return '1234' for 'tst2' variable but it is 50
please do not explain any other way of doing or the explanation which I am not looking for, just I need answer, why tst2 is returning '50', why not '1234' ?
You should send a transaction, not call.
This statement is wrong, it doesn't send a transaction thus doesn't modify burningRatePercent:
token.onlyOwnerSetBurningRate.call(1234)
See here for the correct way of sending a transaction.
In a nutshell: call for read-only operations on blockchain and send for the rest.

My async call is returning before list is populated in forEach loop

I have a routine which gets a list of filenames from the device, then reads the file(s) to build a list. However, the calling routine always returns with zero items. I print the filenames, so I know they exist, however, it appears that the async is returning before I read the files. I used similar code when making an HTTP call. But, something here is causing the routine to return the list even though it hasn't completed. Perhaps, it is possible that I am calling it at the wrong time? I am calling retrieveItems here:
#override
void initState() {
super.initState();
retrieveItems();
}
Eventually I will have a refresh button, but for now I'd simply like the list to populate with the data from the files...
--------------------
Callee
Future<List<String>> readHeaderData() async {
List<String> l = new List();
List<String> files = await readHeaders(); // Gets filenames
files.forEach((filename) async {
final file = await File(filename);
String contents = await file.readAsString();
User usr = User.fromJson(json.decode(contents));
String name = usr.NameLast + ", " + usr.NameFirst;
print(name);
l.add(name);
}
return l;
Caller
void retrieveItems() async {
LocalStorage storage = new LocalStorage();
await storage.readHeaderData().then((item) {
try {
if ((item != null ) &&(item.length >= 1)) {
setState(() {
users.clear();
_users.addAll(item);
});
} else {
setState(() {
_users.clear();
final snackbar = new SnackBar(
content: new Text('No users found.'),
);
scaffoldKey.currentState.showSnackBar(snackbar);
});
}
} on FileNotFoundException catch (e) {
print(e.toString()); //For debug only
setState(() {
_users.clear();
});
});
}
});
This code
Future<List<String>> readHeaderData() async {
List<String> l = new List();
List<String> files = await readHeaders(); // Gets filenames
files.forEach((filename) async {
final file = await File(filename);
String contents = await file.readAsString();
User user = User.fromJson(json.decode(contents));
String name = user.NameLast + ", " + user.NameFirst;
print(name);
l.add(name);
}
return l;
}
returns the list l and then processes the asyc forEach(...) callbacks
If you change it to
Future<List<String>> readHeaderData() async {
List<String> l = new List();
List<String> files = await readHeaders(); // Gets filenames
for(var filename in files) { /// <<<<==== changed line
final file = await File(filename);
String contents = await file.readAsString();
User user = User.fromJson(json.decode(contents));
String name = user.NameLast + ", " + user.NameFirst;
print(name);
l.add(name);
}
return l;
}
the function will not return before all filenames are processed.
files.forEach((filename) async {
means that you can use await inside the callback, but forEach doesn't care about what (filename) async {...} returns.
Also possible
await Future.forEach(yourList, (T elem) async { ...async staff });
To expand on Günter's comment regarding using list.map(f), here's an example of converting a forEach call so that it works correctly.
Broken example
Incorrectly assumes forEach will wait on futures:
Future<void> brokenExample(List<String> someInput) async {
List<String> results;
someInput.forEach((input) async {
String result = await doSomethingAsync(input);
results.add(result);
});
return results;
}
Corrected example
Waits on the async functions to complete, using Future.wait and .map():
Future<void> correctedExample(List<String> someInput) async {
List<String> results;
await Future.wait(someInput.map((input) async {
String result = await doSomethingAsync(input);
results.add(result);
}));
return results;
}
I encountered the similar issue. The problem is that dart will NOT wait for "forEach" contrary to public believe. There are two solutions:
1) Convert forEach to for loop as indicated by others. Another is use Future:
2) await Future.forEach(list, (item) async {
// your code
final result = await getMyResult();
});
Another option
Future.wait(someList.map((item) => something_returns_future(item)));

Firebase python - How to check if field (property) exists in doc

We have read a document from firebase using Python.
doc_ref = db.collection(u'collection_name').document(collection_abc)
doc_fetched = doc_ref.get()
if (doc_fetched.exists):
if (doc_fetched.get('doc_field')):
We get the following error
KeyError("'doc_field' is not contained in the data")
How do we check if doc_field exists in doc_fetched? This document might have some fields populated, and some not populated at the time of read (by design).
We also tried the following with the same error.
if (doc_fetched.get('doc_field') != null):
As you can see from the API documentation for DocumentSnapshot, there is a method to_dict() that provides the contents of a document as a dictionary. You can then deal with it just like any other dictionary: Check if a given key already exists in a dictionary
To solve this, you can simply check the DocumentSnapshot object for nullity like this:
var doc_ref = db.collection('collection_name').doc(collection_abc);
var getDoc = doc_ref.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
if(doc.get('yourPropertyName') != null) {
console.log('Document data:', doc.data());
} else {
console.log('yourPropertyName does not exist!');
}
}
})
.catch(err => {
console.log('Error getting document', err);
});
Or you can use to_dict() method as in the #Doug Stevenson answer