Flutter firebase database iterate over items - firebase-realtime-database

I'd like to know how to get a list of objects from a path on Flutter, here's how i get one object and works fine:
static Future<Year> getYear(
String yearKey) async {
Completer<Year> completer = new Completer<Year>();
FirebaseDatabase.instance
.reference()
.child("year")
.child(yearKey)
.once()
.then((DataSnapshot snapshot) {
var year = new Year.fromJson(snapshot.key, snapshot.value);
completer.complete(year);
});
return completer.future;
}
Now i am trying do this way to get a list of all years in the same path, but not know how iterage over items:
static Future<List<Year>> getYears() async {
Completer<List<Year>> completer = new Completer<List<Year>>();
List<Year> years = new List<Year>();
FirebaseDatabase.instance
.reference()
.child("year")
.once()
.then((DataSnapshot snapshot) {
//how to iterate over the items here?
completer.complete(years);
});
return completer.future;
}
Someone helps me please.

I got this working, for someone that have the same problem:
static Future<List<Year>> getYears() async {
Completer<List<Year>> completer = new Completer<List<Year>>();
List<Year> years = new List<Year>();
FirebaseDatabase.instance
.reference()
.child("year")
.once()
.then((DataSnapshot snapshot) {
//here i iterate and create the list of objects
Map<dynamic, dynamic> yearMap = snapshot.value;
yearMap.forEach((key, value) {
years.add(Year.fromJson(key, value));
});
completer.complete(years);
});
return completer.future;
}
Here is the Year code:
class Year{
String key;
String name;
Year({this.name});
Year.fromJson(this.key, Map data){
name = data['name'];
if(name == null){
name = '';
}
}
}
Thank you everyone.

Related

type 'Null' is not a subtype of type 'Future<DataState<List<CustomerEntity>>>

I'm working on a test CRUD application using Bloc for state management and sqflite database. I was asked to implement BDD testing for the app but I have no idea about BDD testing. From what I've learned so far I tryed to implement a simple scenario for the start which is landing on the home screen, but I'm getting this error when I run the test.
Also I don't know exactly how to mock my database to test the four main Create, Read, Update, and Delete functionalities.
I'm using getIt for dependency injection, Mocktail, and bdd_widget_test.
It is the scenario that I wrote in the .feature file:
Feature: Add Feature
Scenario: Landing on the home screen
Given the app is running
Then I see enabled elevated button
And it's the logic for the test.dart file where I get the mentioned exception:
class MockGetAllCustomers extends Mock implements GetAllCustomersUsecase {}
class MockAddCustomer extends Mock implements AddCustomerUsecase {}
class MockUpdateCustomer extends Mock implements UpdateCustomerUsecase {}
class MockDeleteCustomer extends Mock implements DeleteCustomerUsecase {}
class MockDbHelper extends Mock implements DBHelper {}
void main() {
final GetIt getIt = GetIt.instance;
late CustomersBloc bloc;
late MockGetAllCustomers mockGetAllCustomers;
late MockAddCustomer mockAddCustomer;
late MockUpdateCustomer mockUpdateCustomer;
late MockDeleteCustomer mockDeleteCustomer;
late MockDbHelper dbHelper;
late Database database;
CustomerEntity customer = CustomerEntity(
firstName: 'firstName',
lastName: 'lastName',
dateOfBirth: 'dateOfBirth',
phoneNumber: 'phoneNumber',
email: 'email',
bankAccountNumber: 'bankAccountNumber');
setUpAll(() async {
// Initialize FFI
sqfliteFfiInit();
database = await databaseFactoryFfi.openDatabase(inMemoryDatabasePath);
await database.execute(
'CREATE TABLE $customersTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colFirstName TEXT, $colLastName TEXT, $colDateOfBirth TEXT, $colPhoneNumber TEXT, $colEmail TEXT, $colAccountNum TEXT)');
dbHelper = MockDbHelper();
dbHelper.database = database;
databaseFactory = databaseFactoryFfi;
});
setUp(() {
mockGetAllCustomers = MockGetAllCustomers();
mockAddCustomer = MockAddCustomer();
mockUpdateCustomer = MockUpdateCustomer();
mockDeleteCustomer = MockDeleteCustomer();
bloc = CustomersBloc(mockGetAllCustomers, mockAddCustomer,
mockUpdateCustomer, mockDeleteCustomer);
getIt.registerFactory(() => bloc);
});
group('''Add Feature''', () {
testWidgets('''Landing on the home screen''', (tester) async {
await theAppIsRunning(tester);
await iSeeEnabledElevatedButton(tester);
});
});
}
It's a part of the exception message I'm receiving:
type 'Null' is not a subtype of type 'Future<DataState<List<CustomerEntity>>>'
package:mc_crud_test/features/customer_feature/domain/usecases/get_all_customers_usecase.dart 11:43 MockGetAllCustomers.execute
package:mc_crud_test/features/customer_feature/presentation/bloc/bloc/customers_bloc.dart 43:60 new CustomersBloc.<fn>
package:bloc/src/bloc.dart 226:26 Bloc.on.<fn>.handleEvent
package:bloc/src/bloc.dart 235:9 Bloc.on.<fn>
dart:async
And it's my database class:
String customersTable = 'customers_table';
String colId = 'id';
String colFirstName = 'firstName';
String colLastName = 'lastName';
String colDateOfBirth = 'dateOfBirth';
String colPhoneNumber = 'phoneNumber';
String colEmail = 'email';
String colAccountNum = 'bankAccountNumber';
//
class DBHelper {
//
Database database;
DBHelper({required this.database});
//Database initialization and creation
static Future<Database> initDatabase() async {
//
final dbPath = await sql.getDatabasesPath();
return await sql.openDatabase(
path.join(dbPath, 'customers.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE $customersTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colFirstName TEXT, $colLastName TEXT, $colDateOfBirth TEXT, $colPhoneNumber TEXT, $colEmail TEXT, $colAccountNum TEXT)');
},
version: 1,
);
}
//
//find a customer by firstName, lastName, dateOfBirth, and email
Future<bool> findCustomer(CustomerEntity customer) async {
//
List<Map<String, dynamic>> map = await database.query(customersTable,
columns: [colFirstName, colLastName, colDateOfBirth, colEmail],
where:
'$colFirstName = ? OR $colLastName = ? OR $colDateOfBirth = ? OR $colEmail = ?',
whereArgs: [
customer.firstName,
customer.lastName,
customer.dateOfBirth,
customer.email
]);
if (map.isEmpty) {
return false;
} else {
return true;
}
}
// Add a customer to the Database
Future<int> insertCustomer(CustomerEntity customer) async {
//
final id = await database.insert(
customersTable,
customer.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return id;
}
// Get the list of customers
Future<List<CustomerEntity>> getAllCustomers() async {
//
try {
final List<Map<String, Object?>> queryResult = await database.query(
customersTable,
orderBy: colId,
);
return queryResult.isEmpty
? []
: queryResult.map((e) => CustomerEntity.fromMapObject(e)).toList();
} catch (e) {
print('Error reading database : $e');
return [];
}
}
//
// Delete a Customer
Future<int> deleteCustomer(int id) async {
return await database
.delete(customersTable, where: '$colId = ?', whereArgs: [id]);
}
//
//Update a Customer
Future<bool> updateCustomer(CustomerEntity customer) async {
try {
final count = await database.update(customersTable, customer.toMap(),
where: '$colId = ?', whereArgs: [customer.id]);
if (count == 1) {
return true;
} else {
return false;
}
} catch (e) {
print('Failed to update the customer: $e');
return false;
}
}
}

After web request, navigate to other view (async /await proper usage) - Flutter , dart

I want to fetch data from the server, then parse json. When they are done, I want to navigate to another view.
void getServerData() async{
WebRequests ws = WebRequests('https://sampleurl');
Map<String, dynamic> map = await ws.getData();
mychamplist = map['mychampionships'];
mychamplist.forEach((f){
mychampionships.add(MyChampionships(
name: f['name'],
id: int.parse(f['id']),
numberOfPlayers: int.parse(f['nofplayers']),
));
});
Navigator
.of(context)
.pushReplacement(new MaterialPageRoute(builder: (BuildContext context) {
return FantasyNbi();
}));
}
It navigates to the FantasyNbi class before the previous code finished.
How could it do in proper way?
I do have a example class for you that you could use:
class API {
static Future getData(String url) {
return http.get('api link' + url);
}
static Future<List<BasicDiskInfo>> fetchAllDisks() async {
final response = await getData('disk');
if (response.statusCode == 200) {
Iterable list = json.decode(response.body);
List<BasicDiskInfo> disks =
list.map((model) => BasicDiskInfo.fromJson(model)).toList();
return disks;
} else {
throw Exception('Failed to load disks');
}
}
static Future<Disk> fetchDisk(int id) async {
final response = await getData('disk/' + id.toString());
if (response.statusCode == 200) {
return Disk.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load disk');
}
}
}
class Disk {
int id;
String name;
String volumeLable;
bool isReady;
String driveType;
String driveFormat;
int totalSize;
int totalFreeSpace;
int availableFreeSpace;
Disk(
{this.id,
this.name,
this.volumeLable,
this.isReady,
this.driveType,
this.driveFormat,
this.totalSize,
this.totalFreeSpace,
this.availableFreeSpace});
factory Disk.fromJson(Map<String, dynamic> json) {
return Disk(
id: json['id'],
name: json['name'],
volumeLable: json['volumeLable'],
isReady: json['isReady'],
driveType: json['driveType'],
driveFormat: json['driveFormat'],
totalSize: json['totalSize'],
totalFreeSpace: json['totalFreeSpace'],
availableFreeSpace: json['availableFreeSpace']);
}
}
And to get the data I can do this:
var data = await API.fetchAllDisks();
// or
API.fetchAllDisks().then((response) => {/* do something */})

How to display variable from json return in text

String empName;
Future<List> getUserData() async{
final response = await http.post("http://172.16.161.34:8080/ebs/cfs/android_test_app/accessfile.php?q=getUserData",body:{
"emp_id": widget.empId,
});
var dataUser = jsonDecode(response.body);
empName = dataUser[0]['name'];
return null;
}
How to display the variable "empName" in line 2 to line 70 "child: Text('')"
Full code on Pastebin
Try this way.. make pojo class for response data like this way..
class UserData {
final int albumId;
final int id;
final String title;
final String url;
final String thumbnailUrl;
UserData({this.albumId, this.id, this.title, this.url, this.thumbnailUrl});
factory UserData.fromJson(Map<String, dynamic> json) {
return new UserData(
albumId: json['albumId'],
id: json['id'],
title: json['title'],
url: json['url'],
thumbnailUrl: json['thumbnailUrl']);
}
}
make method for api call..
Future<UserData> fetchData() async {
var result = await get('https://jsonplaceholder.typicode.com/photos');
if (result.statusCode == 200) {
return UserData.fromJson(json.decode(result.body));
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to load post');
}
}
after that make global object that fetch data..
Future<UserData> userDataList;
on Button click ..
userDataList = fetchData();
after that you want to print data..
userDataList.then((userData){
print(userData.title);
});
First of all you getUserData() function never returns anything. It seems like you only need the name so this function could look like this:
Future<String> getUserData() async{
final response = await http.post("http://172.16.161.34:8080/ebs/cfs/android_test_app/accessfile.php?q=getUserData",body:{
"emp_id": widget.empId,
});
var dataUser = jsonDecode(response.body);
return dataUser[0]['name'];
}
Then to set the empName variable you should use setState().
So change your afterFirstLayout() method to this:
#override
void afterFirstLayout(BuildContext context) async {
// Calling the same function "after layout" to resolve the issue.
getUserData().then( (userName) {
setState(() {
empName = userName;
});
});
}
Also you seem to want to reload the name once you press the IconButton.
So you might want to override your code with this:
IconButton(icon: Icon(Icons.shopping_cart),
onPressed:() {
getUserData().then( (userName) {
setState(() {
empName = userName;
});
});
},
),

Flutter FutureBuilder Not Updating

I have a Flutter FutureBuilder that needs to be updated with new data given by the user. However, the UI elements in the FutureBuilder do not update and still contain the old values. I have checked through print statements that the new data is correctly loaded. The issue seems to be with FutureBuilder rebuilding the widget when the new data is loaded. Any help is appreciated.
Future<List<PollItem>> fetchPost(String loc) async {
return new Future(() async {
final response = await http
.post(restip + '/getPosts',
body: {"data": loc});
if (response.statusCode == 200) {
print(response.body);
// If the call to the server was successful, parse the JSON
// This function adds json to list
PollItem.fromJson(json.decode(response.body));
// list is a list of posts gathered based on the string criteria
return list;
} else {
throw Exception('Failed to load polls');
}
});
}
class PollState extends State<Poll> {
TextEditingController textc = new TextEditingController();
static String dropDowntext = "City";
String _name = "Search";
final _names = [''];
Widget build(BuildContext context) {
print("dropdown"+dropDowntext);
textc.text = _name;
print(dropDowntext);
return FutureBuilder<List<PollItem>>(
future: fetchPost(dropDowntext),
initialData: [PollItem()],
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot.data[0].question);
});
}
Here is my global file:
List<PollItem> list = new List();
factory PollItem.fromJson(Map<String, dynamic> json) {
int len = json['length'];
if(listNum!=len) {
listNum = len;
list.clear();
for (int i = 0; i < len; i++) {
list.add(PollItem(
answer1: json[i.toString()]['answer1'],
location: json[i.toString()]['location']
)
);
}
}
}
You don't need to create a Future object :
Future<List<PollItem>> fetchPost(String loc) async {
final response = await http.post(restip + '/getPosts',body: {"data": loc});
if (response.statusCode == 200) {
print(response.body);
final data = json.decode(response.body);
int len = data['length'];
final List<PollItem> newList = List();
for (int i = 0; i < len; i++) {
newList.add(PollItem(
answer1: data[i.toString()]['answer1'],
location: data[i.toString()]['location']
)
);
}
print("new list size: ${newList.length}");
return newList;
} else {
throw Exception('Failed to load polls');
}
return null;
}

Flutter and Firebase: How can i receive data from function?

i use the following function to fetch userData from Firestore:
Future<String>getRegisterUserData({String userID}) async {
Firestore.instance.collection("Users").document(userID).get().then(
(datasnapshot) {
if (datasnapshot.exists) {
return datasnapshot.data['Email'];
} else {
return "Loading...";
}
},
);
}
I execute this function on my UserProfilePage like this:
_email = widget.firestore.getRegisterUserData(widget.userID).toString();
But i always get the print statement: Instance of 'Future' and not the saved email-address...
i also try this:
Future<String> getRegisterUserData({String userID}) async {
String email;
Firestore.instance.collection("Users").document(userID).get().then(
(datasnapshot) {
if (datasnapshot.exists) {
email = datasnapshot.data['Email'];
} else {
email = "Loading...";
}
},
);
return email;
}
The Print Statement is always the same...
So where is my mistake? I want to display the Value of 'Email' on the UserProfilePage in a variable, or is my firestore function incorrect?
Thank you for our help
Add await keyword. But at a different place.
tempEmail = await widget.firestore.getRegisterUserData(widget.userID);
setState(() {
_email = tempEmail;
});
// we don't need toString as getRegisterUserData itself is returning Future<String>
Note for using await: As we are using await the method/function which contains this should have a async in its signature.
Or you can use then block
widget.firestore.getRegisterUserData(widget.userID).then((email) {
setState(() {
_email = email;
});
});
Explanation: widget.firestore.getRegisterUserData(widget.userID) is of type Future<String>. that's why it is printed as Instance of 'Future'. We have to convert the Future to String by await or by then block
Using SharedPreferences:
Future<String> getEmail() async {
final prefs = await SharedPreferences.getInstance();
String email = prefs.getString('email');
if (email != null) {
return email;
} else {
email = await widget.firestore.getRegisterUserData(widget.userID);
prefs.setString('email', email); //update shared preferences
return email;
}
}
// usage (may be in initState)
widget.getEmail().then((email) {
setState(() {
_email = email;
})
})
Updated
Based on your information, you need a FutureBuilder in order to wait the response to build your widget:
return FutureBuilder(
future: getRegisterUserData(userID: "1234"),
builder: (context, asyncsnapshot){
return asyncsnapshot.hasData && asyncsnapshot.data != null ? TextFormField(
initialValue: asyncsnapshot.data,
) : CircularProgressIndicator();
}
);

Resources