Flutter extract async to separator class and methods - asynchronous

My implementation repository
in main method i have this lines of code to connect to database and work fine
Future<void> main() async {
final database = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
final userDao = database.userDao;
runApp(MaterialApp(...);
}
now i'm trying to use this codes
final database = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
final userDao = database.userDao;
from class, for example:
Future<void> main() async {
MyDatabase myDatabase = MyDatabase();
final userDao = myDatabase.userDao;
runApp(MaterialApp(...);
}
unfortunately i get null for userDao in this implementation, i think in that witch i use async i should be change that and use then()
class MyDatabase {
UserDao userDao;
Future<UserDao> initialDatabase() async {
final database = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
return database.userDao;
}
}
#dao
abstract class UserDao{
#Query('SELECT * FROM User LIMIT 1')
Stream<User> getUserInfo();
#insert
Future<void> insertUserInformation(User user);
}
UPDATED: implementation solution on scope model
class MydbModel extends Model {
MyDatabase myDatabase = MyDatabase();
Future _doneFuture;
MydbModel() {
_doneFuture= myDatabase.initialDatabase();
}
Future get initializationDone => _doneFuture;
}
class MyDatabase {
AppDatabase db;
UserDao userDao;
Future<void> initialDatabase() async {
db = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
}
UserDao getUserDao() {
return db.userDao;
}
}
main() {
runApp(MaterialApp(
title: 'floor sample',
home: App(),
));
}
class App extends StatefulWidget {
App({Key key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ScopedModel(
model: MydbModel(),
child: ScopedModelDescendant<MydbModel>(
builder: (context, _, model) => StreamBuilder<User>(
stream: model.myDatabase.userDao.getUserInfo(),
builder: (_, snapshot) {
if (!snapshot.hasData) {
return Text('user not found');
} else {
return Text('user found');
}
},
),
),
),
);
}
}

With given class implementation, you should call the function and wait until it completes:
MyDatabase myDatabase = MyDatabase();
final userDao = await myDatabase.initialDatabase();
Alternatively, if you don't want to recreate databse instance each time, consider assigning it to a class member
class MyDatabase {
AppDatabase db;
Future<void> initDb() async {
db = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
}
UserDao getUserDao() {
return db.userDao;
}
// here may be other functions that use db
}
And the use it like this
MyDatabase myDatabase = MyDatabase();
await myDatabase.initDb();
final userDao = myDatabase.getUserDao();
// here you can call other functions from class

Related

The argument type 'Object? Function()' can't be assigned to the parameter type 'Map<String, dynamic>'

I created a class called 'tasks' and implement it. I edit gradle files for connecting with firebase. I gain some errors in my code. So please help me to solve this error.
class _MyHomePageState extends State<MyHomePage> {
late List<Task> items;
FirestoreService fireServ = new FirestoreService();
late StreamSubscription<QuerySnapshot> todoTasks;
#override
void initState() {
super.initState();
items= [];
todoTasks.cancel();
todoTasks=fireServ.getTaskList().listen((QuerySnapshot snapshot){
final List<Task> tasks=snapshot.docs
.map((documentSnapshot) => Task. fromMap(documentSnapshot.data))
.toList();
setState(() {
this.items = tasks;
});
});
}
This is my firestore class service
class FirestoreService {
Future<Task> createTODOTask(String taskname, String taskdetails,String taskdate,String tasktime,String tasktype) async {
final TransactionHandler createTransaction = (Transaction tx) async {
final DocumentSnapshot ds = await tx.get(myCollection.doc());
final Task task = new Task(taskname, taskdetails,taskdate,tasktime,tasktype);
final Map<String, dynamic> data = task.toMap();
await tx.set(ds.reference, data);
return data;
};
return FirebaseFirestore.instance.runTransaction(createTransaction).then((mapData) {
return Task.fromMap(mapData);
}).catchError((error) {
print('error: $error');
return null;
});
}
Stream<QuerySnapshot> getTaskList({int offset=0, int limit=0}) {
Stream<QuerySnapshot> snapshots = myCollection.snapshots();
if (offset != null) {
snapshots = snapshots.skip(offset);
}
if (limit != null) {
snapshots = snapshots.take(limit);
}
return snapshots;
}
}
You need to change:
documentSnapshot.data
to this:
documentSnapshot.data()
.data() is a method and not a property of the DocumentSnapshot object.

Different screen based on user role not working

Hi I'am new to Flutter making an app for booking appointments which require to screens for two types of users i.e. patient and doctor.
class DashboardPage extends StatefulWidget {
#override
_DashboardPageState createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
UserProvider userProvider;
final AuthMethods _authMethods = AuthMethods();
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) async {
userProvider = Provider.of<UserProvider>(context, listen: false);
await userProvider.refreshUser();
_authMethods. getUserDetails();
});
}
User user = User();
#override
Widget build(BuildContext context) {
if (user.role == 'patient') {
return PatientHomePage();
}
else if (user.role == 'doctor') {
return DoctorHomePage();}
return Container(color: Colors.red,);
}
}
role variable is defined in another dart file:
class User { String uid; String name; String email; String role = "patient"; String profilePhoto; User({ this.uid, this.name, this.email, this.role, this.profilePhoto, }); ........... }
the default value "patient" is assigned to it when a user logs in. Future<void> addDataToDb(FirebaseUser currentUser) async { User user = User( uid: currentUser.uid, email: currentUser.email, name: currentUser.displayName, profilePhoto: currentUser.photoUrl, role: "patient"); firestore .collection(USERS_COLLECTION) .document(currentUser.uid) .setData(user.toMap(user)); } all this is happening in another dart file
But this logic is not working as expected as it's showing only the red screen on phone which implies that
there is some issue in getting user.role from firebase.
Please help me...
class DashboardPage extends StatefulWidget {
#override
_DashboardPageState createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
UserProvider userProvider;
final AuthMethods _authMethods = AuthMethods();
User user = User();
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) async {
userProvider = Provider.of<UserProvider>(context, listen: false);
/// This method is future method so it might happen that after widget render you are getting response.
await userProvider.refreshUser();
/// seState will rebuild your widget with new user details
setState(() {
user = _authMethods. getUserDetails();
});
});
}
#override
Widget build(BuildContext context) {
if (user.role == 'patient') {
return PatientHomePage();
}
else if (user.role == 'doctor') {
return DoctorHomePage();}
return Container(color: Colors.red,);
}
}

Flutter access firebase user email from different class

I am stuck for hours now on this problem.
I have no problem to access the final currentUser = loggedInUser.email;when the getCurrentUser function is defined and called in the same class (SlateScreen).
My SlateScreen Class ad MessageStream Class are both in the same .dart file. So this here works:
final firestore = Firestore.instance;
/// loggedInUser variable for fetching the user email later
FirebaseUser loggedInUser;
DatabaseMethods databaseMethods = DatabaseMethods();
class TheSlateScreen extends StatefulWidget {
static String id = 'theslate_screen';
#override
_TheSlateScreenState createState() => _TheSlateScreenState();
}
class _TheSlateScreenState extends State<TheSlateScreen> {
final _auth = FirebaseAuth.instance;
// call getCurrentUser in initState
#override
void initState() {
super.initState();
getCurrenUser();
}
getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
print(loggedInUser);
}
} catch (e) {
print(e);
}
}
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
// fetch the email from my getCurrentUser function
final currentUser = loggedInUser.email;
But I get "The getter email was called on null" error if I define my getCurrentUser() function in the DatabaseMethods Class, and then call it in the SlateScreen class' via
#override
void initState() {
super.initState();
databaseMethods.getCurrentUser(_auth);
}
My DatabaseMethods Class:
class DatabaseMethods {
FirebaseUser loggedInUser;
getCurrentUser(FirebaseAuth _auth) async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
I tried all kind of adjustments, but didn t get anywhere...
UPDATE / SOLUTION:
Thanks to the the anwsers provided, I found a way:
in my DatabaseMethods class I simply return the user:
Class DatabaseMethods {
getCurrentUser(FirebaseAuth _auth) async {
try {
final user = await _auth.currentUser();
if (user != null) {
return user;
}
} catch (e) {
print(e);
}
}
}
and in the SlateScreen Class, I am using a helper function that I can call in initState():
#override
void initState() {
super.initState();
logInUser(_auth);
}
logInUser(_auth) async {
loggedInUser = await DatabaseMethods().getCurrentUser(_auth);
}
In this snippet:
// fetch the email from my getCurrentUser function
final currentUser = loggedInUser.email;
The loggedInUser variable is only set once the getCurrentUser method has completed. So you can't just do loggedInUser.email anywhere in your code, but can only do that after you've made sure getCurrentUser has completed.
So this would work fine:
await databaseMethods.getCurrentUser(_auth);
final currentUser = loggedInUser.email;
Given what you shared, it may be able to add that await in your initState:
#override
void initState() {
super.initState();
await databaseMethods.getCurrentUser(_auth);
}
You need to initialize loggedInUser, just do the following:
loggedInUser = await FirebaseAuth.instance.currentUser();
final currentUser = loggedInUser.email;

Dart make a singletone without using async and await outside

i'm trying to make a singleton class from MyDatabase with default constructor to access to getUserDao on this class, implemented code work fine but i have some other issue with that as
i have to use async and await in outside of class
i have to use that
only on main function because main method should be async
for example:
my singleton class:
class MydbModel {
UserDao _userDao;
MydbModel._(this._userDao);
static Future<MydbModel> create() async => MydbModel._(await initialDatabase());
static Future<UserDao> initialDatabase() async {
var db = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
return db.userDao;
}
UserDao get userDao=>_userDao;
}
and main class:
main() async {
var mydbModel = await MydbModel.create();
print(mydbModel.userDao);
}
i have to define that only on main method and i can't use that on StatefulWidget or State<Classname classes and when i try to use this instance i have to pass it for all class
how can i resolve this problem to use simply class in all part of application?
for example:
main() {
var userDao = MydbModel.create().then((dao){
return dao;
});
print(userDao);
}
Try this:
class MydbModel {
static UserDao _userDao;
MydbModel._();
static UserDao get userDao =>_userDao;
static Future<void> create() async => _userDao = await initialDatabase();
static Future<UserDao> initialDatabase() async {
var db = await $FloorAppDatabase.databaseBuilder('flutter_database.db').build();
return db.userDao;
}
}
You should be able to access _userDao from anywhere using MydbModel.userDao.
If your app requires data to be loaded asynchronously before being ready for user input, you'll have to show some UI while it loads. FutureBuilder handles such a case.
Here's an example that shows a spinner until prepareData is finished. Another option would be to show a splash screen.
Future prepareData() async => null;
...
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: prepareData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
} else
return Center(child: CircularProgressIndicator());
},
);
}

Flutter: how to create a Singleton from an http request

I want to declare a user object, that I will instantiate with an http request, and I want it to be global. How can I do it? With a Singleton? But how can I make this class also a Singleton? Or is there another way?
That is what I've done so far:
class User{
String username;
String password;
int id;
User({this.username, this.id});
factory User.fromJson(Map<String, dynamic> json){
return User(
username: json['name'],
id: json['id']
);
}
}
and then:
var user = await login(username, password, context);
In flutter, you should not make singletons. Instead, you should store it into a widget that exposes these data to all of its descendants.
Usually InheritedWidget
The reason being, with such architecture all the descendants are automatically aware of any change made to your "singleton".
A typical example would be the following:
#immutable
class User {
final String name;
User({this.name});
}
class Authentificator extends StatefulWidget {
static User currentUser(BuildContext context) {
final _AuthentificatorScope scope = context.inheritFromWidgetOfExactType(_AuthentificatorScope);
return scope.user;
}
final Widget child;
Authentificator({this.child, Key key}): super(key: key);
#override
_AuthentificatorState createState() => _AuthentificatorState();
}
class _AuthentificatorState extends State<Authentificator> {
#override
Widget build(BuildContext context) {
return _AuthentificatorScope(
child: widget.child,
);
}
}
class _AuthentificatorScope extends InheritedWidget {
final User user;
_AuthentificatorScope({this.user, Widget child, Key key}) : super(child: child, key: key);
#override
bool updateShouldNotify(_AuthentificatorScope oldWidget) {
return user != oldWidget.user;
}
}
which you have to instantiate like this:
new MaterialApp(
title: 'Flutter Demo',
builder: (context, child) {
return Authentificator(
child: child,
);
},
home: Home(),
);
and then use inside your pages like this:
#override
Widget build(BuildContext context) {
User user = Authentificator.currentUser(context);
...
}

Resources