I added firestore and autologin with google to my app. So now when the user starts the app he get for a few seconds a grey screen. How can I create a loading screen, so that the user see that something happen instead of having a grey screen?
My code when starting the app looks like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FireBaseHandler handler = new FireBaseHandler();
GoogleSignIn _googleSignIn = GoogleSignIn();
print("Debug 1");
_googleSignIn.isSignedIn().then((logged) async => {
print("Debug 2" + logged.toString()),
if (!logged)
{
print("Debug 3"),
runApp(MyApp(screen: WelcomeScreen())),
}
else
{
print("Debug 4"),
_googleSignIn.signInSilently().then((user) => {
print(user.toString()),
handler
.is_user_registered(user.email.toString())
.then((value) => {
print("Debug 5"),
print(value.docs[0].data().toString()),
UserManager.userdata = value.docs[0].data(),
runApp(MyApp(screen: NavBar())),
})
})
}
});
}
You should move the async calls into the MyApp widget and use a FutureBuilder to wait for the results.
You then show the loading indicator when the data is null.
Check out this update to your code:
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
FireBaseHandler handler = new FireBaseHandler();
GoogleSignIn _googleSignIn = GoogleSignIn();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder<bool>(
future: _googleSignIn.isSignedIn(),
builder: (BuildContext context, AsyncSnapshot<bool> signInSnapshot) {
if (signInSnapshot.data == null) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else {
bool isLoggedIn = signInSnapshot.data;
if (!isLoggedIn) {
return WelcomeScreen();
} else {
return FutureBuilder<GoogleSignInAccount>(
future: _googleSignIn.signInSilently(),
builder: (_,
AsyncSnapshot<GoogleSignInAccount>
signInSilentlySnapshot) {
if (signInSilentlySnapshot.data == null) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else {
GoogleSignInAccount user = signInSilentlySnapshot.data;
return FutureBuilder(
future:
handler.is_user_registered(user.email.toString()),
builder: (_, isUserRegisteredSnapshot) {
if (isUserRegisteredSnapshot.data == null) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else {
var value = isUserRegisteredSnapshot.data;
UserManager.userdata = value.docs[0].data();
return NavBar();
}
});
}
});
}
}
},
),
);
}
}
Related
The problem is that Hive is acting unexpectedly, and when the app closes or I restart it all, the data in the box is cleared.
main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
await Firebase.initializeApp();
await Hive.initFlutter();
Hive.registerAdapter(CredentialsModelAdapter());
Hive.registerAdapter(DoctorModelAdapter());
Hive.registerAdapter(DuserModelAdapter());
Hive.registerAdapter(DoctorAppointmentsAdapter());
Hive.registerAdapter(AppointmentStatusesAdapter());
Hive.registerAdapter(AccountTypeAdapter());
Hive.registerAdapter(UserAdapter());
await Hive.openBox<CredentialsModel>("cred");
await Hive.openBox<DuserModel>("doctor");
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _appRouter = app_router.AppRouter();
#override
Widget build(BuildContext context) {
return MaterialApp.router(
title: "x",
debugShowCheckedModeBanner: false,
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
);
}
}
Here is where I fetch the data from the api and store it in box:
#override
Future<Either<ApiFailures, dynamic>> signInWithEmailAndPassword(
{required String email, required String password}) async {
late Box<CredentialsModel> credentials;
var result;
try {
final response = await http.get(Uri.parse(
"xxxxxxxx"));
if (response.statusCode == 200) {
result = await json.decode(response.body);
if (result["AZSVR"] == "FAILED") {
return const Left(ApiFailures.authFailed());
} else {
var content = CredentialsModel.fromJson(result);
credentials = Hive.box("cred");
credentials.put('cred', content);
return right(result["api_token"]);
}
}
} on SocketException catch (e) {
return const Left(ApiFailures.noConnection());
} on HttpException {
return const Left(ApiFailures.notfound());
} catch (_) {
return const Left(ApiFailures.notfound());
}
return Right(result["api_token"]);
}
Where I call the box:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vwelfare/application/provider/doctor.repository.provider.dart';
import 'package:vwelfare/domain/models/doctor/duser.dart';
import '../../domain/models/credentials/credentials.dart';
class MyWidget extends HookConsumerWidget {
const MyWidget({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final Box<CredentialsModel> credBox = Hive.box("cred");
final Box<DuserModel> doctorBox = Hive.box("doctor");
final controller = useTextEditingController();
final uid = useState(0);
final cred = useState(const CredentialsModel());
return Scaffold(
body: ValueListenableBuilder(
valueListenable: credBox.listenable(),
builder: (context, Box<CredentialsModel> box, _) {
final cred = box.get("cred");
print(cred!.api_token);
final doctor = ref.watch(getDoctor(cred.api_token!));
return doctor.when(
data: (data) => data.fold(
(l) => ValueListenableBuilder(
valueListenable: doctorBox.listenable(),
builder: (context, Box<DuserModel> box, _) {
final model = box.get("doctor");
final doctor = model!.User;
if (doctor != null) {
return Center(
child: Text("${doctor.address}"),
);
} else {
return const Center(
child: Text("CONTACT US"),
);
}
}),
(r) => Center(child: Text("${r.User!.name}"))),
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) {
print(error);
return Center(
child: Text("$error hello"),
);
});
},
),
);
}
}
I don't know if I am doing something wrong but I followed the docs as they say:
1- registered the adapter
2- opened the box
3- called it in a widget
What am I doing wrong?
I have a problem with flutter printing out the name and rendering Widget name after running the application
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
Home();
} else {
return LoginOrSignup();
}
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body:SafeArea(
child: FutureBuilder(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else {
return LoginOrSignup();
}
}),
))
);
}
}
After running the app the output is LoginOrSignup()
class LoginOrSignup extends StatelessWidget {
const LoginOrSignup({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Login()),
);
},
child: Text('Loginsss'),
),
),
Center(
child: MaterialButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Signup()),
);
},
child: Text('Signup'),
),
)
],
),
);
}
}
I have tried using another widget like Text() but it still prints out the same when i run the application on a mobile app. The problem seems to appear in the autoLogin() function that i have
The issue is your future return Widget itself, and when you use Text('${snapshot.data}') it print the widget, To simplfity this you can return data from Future(this is what mostly we do). Let say you like to return widget itself.
A little correction is needed on Future.
Future<Widget> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
return Home();
} else {
return LoginOrSignup();
}
}
And
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: FutureBuilder<Widget>(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return LoginOrSignup();
}
}),
)));
You are returning a Widget in autoLogin function. Instead you should return a bool.
Future<bool?> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == null) return null;
if (loggedIn == true) {
return true;
} else {
return false;
}
}
Then in the FutueBuilder you can check if it's then return Home()
if (snapshot.hasData && snapshot.data! == true) {
return Home();
} else {
return LoginOrSignup();
I want to get the bool of a shared pref to decide which Widget should get loaded, but the method cant be async or to bool cant get the value because it is not allowed to "await" the value. I have tried fixing it, but it mostly fails because "home" can't receive a future widget..., is there another way how I could do this?
void main() => runApp(MyApp());
setloginbool() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("savelogin", true);
}
Future<bool> getloginbool() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool savelogin = prefs.getBool("savelogin") ?? false;
return savelogin;
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KHS Plan',
theme: (ThemeData(
textTheme: const TextTheme(
bodyText1: TextStyle(fontSize: 14)
)
)),
home: checkifpassword(),
);
}
}
Widget checkifpassword() {
bool s = await getloginbool();
if(s){
return const Login();
} else {
return const MyHomePage();
}
}
//This does not work as well
checkifpassword() async {
bool s = await getloginbool();
if(s){
return const Login();
} else {
return const MyHomePage();
}
}
You can use FutureBuilder on Home
Future<bool> checkifpassword() async {
//perfrom your async operation and return bool
return await Future.delayed(Duration(seconds: 2), () {
return true;
});
}
And home
home: FutureBuilder<bool>(
future: checkifpassword(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {// for true
return Login();;
} else return MyHomePage();
}
/// check others state
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
)
I have code that I use to write data to a file (such as global settings for an application). I write down the data and see. But the problem is that I do not understand how to take these mini data from another page. For example I wrote the word "Test" and I want and I want to assign that word to some variable in another page. I will be grateful for your help. Here is my code:
import 'dart:io';
import 'dart:async';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Reading and Writing to Storage",
home: Home(
storage: Storage(),
),
);
}
}
class Home extends StatefulWidget {
final Storage storage;
Home({Key key, #required this.storage}) : super(key: key);
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
TextEditingController controller = TextEditingController();
String state;
Future<Directory> _appDocDir;
#override
void initState() {
super.initState();
widget.storage.readData().then((String value) {
setState(() {
state = value;
});
});
}
Future<File> writeData() async {
setState(() {
state = controller.text;
controller.text = '';
});
return widget.storage.writeData(state);
}
void getAppDirectory() {
setState(() {
_appDocDir = getApplicationDocumentsDirectory();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Reading and Writing Files'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('${state ?? "File is Empty"}'),
TextField(
controller: controller,
),
RaisedButton(
onPressed: writeData,
child: Text('Write to File'),
),
RaisedButton(
child: Text("Get DIR path"),
onPressed: getAppDirectory,
),
FutureBuilder<Directory>(
future: _appDocDir,
builder:
(BuildContext context, AsyncSnapshot<Directory> snapshot) {
Text text = Text('');
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
text = Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) {
text = Text('Path: ${snapshot.data.path}');
} else {
text = Text('Unavailable');
}
}
return new Container(
child: text,
);
},
)
],
),
),
);
}
}
class Storage {
Future<String> get localPath async {
final dir = await getApplicationDocumentsDirectory();
return dir.path;
}
Future<File> get localFile async {
final path = await localPath;
return File('$path/db.txt');
}
Future<String> readData() async {
try {
final file = await localFile;
String body = await file.readAsString();
return body;
} catch (e) {
return e.toString();
}
}
Future<File> writeData(String data) async {
final file = await localFile;
return file.writeAsString("$data");
}
}
And scrin:
You can pass the value to other page using Navigator and parameter.
class NewScreen extends StatelessWidget {
final String data;
NewScreen({this.data});
...
}
When you move to 'NewScreen' by using Navigator at your 'Home' page,
pass the data what you transfer.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewScreen(data: 'Test'),
),
);
i have the below code , that when the button is pressed on Page1() , the future is executed when Page2() loads, but the CircularProgressIndicator() "freezes", until the future completes. I have tried this with BottomNavigationBar as well, and the "slide-in" freezes half way there as well.
is there a more idiomatic way to do this so that Page2() renders fully while the future is running ?
//-----------------------
//main.dart
//-----------------------
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
routes: {
'/' : (BuildContext context) => Page1(),
'/page2' : (BuildContext context) => Page2()
}
);
}
}
//-----------------------
//page1.dart
//-----------------------
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page 1')),
body: Container(
child: Column(children: <Widget>[
Text('Page 1 header'),
RaisedButton(
child: Text('Click me'),
onPressed: () {
Navigator.of(context).pushReplacementNamed('/page2');
})
],),)
);
}
}
//-----------------------
//page2.dart
//-----------------------
class Page2 extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _Page2State();
}
}
class _Page2State extends State<Page2> {
MainModel model = MainModel();
void initState() {
model.fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page 2')),
body: ScopedModel<MainModel>(
model: model,
child: ScopedModelDescendant(
builder: (context, child, MainModel model) {
if (model.isLoading) {
return Center(child: CircularProgressIndicator());
} else {
return Container(
child: Column(
children: <Widget>[
Text('Page 2 body'),
],
));
}
})));
}
}
//-----------------------
//main_model.dart
//-----------------------
class MainModel extends Model {
bool _isLoading = false;
bool get isLoading => _isLoading;
void fetchData() async {
_isLoading = true;
notifyListeners();
final String url = 'https://jsonplaceholder.typicode.com/todos/1';
await http.get(url)
.then<Null>((http.Response response) {
print('${DateTime.now()} In http response and about to sleep');
sleep(const Duration(seconds:5));
print('${DateTime.now()} done sleeping');
_isLoading = false;
notifyListeners();
return;
}
).catchError((error) {
print('Error: $error');
_isLoading = false;
notifyListeners();
return;
});
}
}
The problem is that the sleep() method freezes the ui.
You could try a Future.delayed() in the following way:
class MainModel extends Model {
bool _isLoading = false;
bool get isLoading => _isLoading;
void fetchData() {
_isLoading = true;
notifyListeners();
final String url = 'https://jsonplaceholder.typicode.com/todos/1';
http.get(url).then<Null>((http.Response response) {
print('${DateTime.now()} In http response and about to sleep');
Future.delayed(Duration(seconds: 5), () {
_isLoading = false;
notifyListeners();
print('${DateTime.now()} done sleeping');
});
}).catchError((error) {
print('Error: $error');
_isLoading = false;
notifyListeners();
return;
});
}
}
Flutter recommends using the FutureBuilder widget when working with async data sources. For example:
FutureBuilder<Post>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner
return CircularProgressIndicator();
},
);
This way the CircularProgressIndicator will keep running whilst your Page2 loads the API data.