setState is not updating the UI even though state is updating - flutter

I am trying to wait till amplify configuration is done then load the login screen. Even though state seems to be getting updated I am still getting the loadinscreen. Why is that?
I am not sure if setState is proper method on the init : Importance of Calling SetState inside initState
As per the doc : https://docs.amplify.aws/start/getting-started/integrate/q/integration/flutter/#configure-amplify
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isAmplifyConfigured = false;
late AmplifyAuthCognito auth;
#override
void initState() {
_initializeApp();
super.initState();
}
Future<void> _initializeApp() async {
await _configureAmplify();
setState(() {
_isAmplifyConfigured = true;
});
}
Future<void> _configureAmplify() async {
auth = AmplifyAuthCognito();
try {
await Amplify.addPlugin(auth);
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
print(
'Amplify was already configured. Looks like app restarted on android.');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: AppRoutes.onGenerateRoute,
initialRoute: _isAmplifyConfigured
? LoginScreen.routeName
: LoadingScreen.routeName,
);
}
}

I think the issue is with you trying to reassign your initialRoute. I'm not super familiar with this property, but given the name I assume this is set once and is not rebuilt, not even when the state changes. It would make sense, also, because the rest of your code sounds like it should work.
Before trying anything else, I'd recommend you move your logic to initialize Amplify to the LoginScreen, and having its body depend on the _isAmplifyConfigured boolean value. So show spinner if it's false, and show Login fields when it's true.
Even better would be to create a HomeScreen, so you can keep this Amplify initialization at the bottom of your app's stack. And then have your HomeScreen either show the Login widgets, the home screen of your app, or a loading state.

Related

What should I do when I have to use "void function() async" as a last resort in Flutter?

My function is as follows:
void function() async {
…
}
I used the above function in Widget build(BuildContext context).
Actually, I want it to be Future<void> function() async instead of void function() async.
However, I can't use await in Widget build(BuildContext context), so I can't use Future<void> function() async because Future requires await.
I shouldn't use FutureBuilder or then because I don't want to get the data in the function, I just want to run the function.
Appreciate if someone can advise. Thank you in advance!
You can define your function as future & call it in initState of StatefulWidget (without await)
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<void> myFutureFunction() async {
// some future work with
}
#override
void initState() {
super.initState();
// this will be called once, when this widget is apear to the screen
myFutureFunction();
}
#override
Widget build(BuildContext context) {
return SizedBox();
}
}
build method can run up to 60(maybe more with the newest phones)times per second, so it's not a good idea to run any heavy computation or API call in the build method. It's simply for building(widgets).
You may want to rethink your app architecture if you think you must.
Consider bloc pattern or just checkout other lifecycle methods for stateful widgets. This article has a list of them.
As for the question, async functions technically don't require you to await them. You can just call them without adding await in front of them.
If you have a warning, it might be coming from lint rules. Related rule.

Flutter Cubit InitState

I am at the begin of my Cubit learning and i tried to create a "Liter-Tracker" with sharedPrefs. Everything works but not the init state. I have to press a Button first because I initialize the drinkValue with 0. I tried to return an Int with the value from shared prefs but this dont work :( May you help me?
This is my cubit:
class DrinkCubit extends Cubit<DrinkState> {
DrinkCubit() : super(DrinkState(drinkValue: 0));
Future<void> loadCounter() async {
final prefs = await SharedPreferences.getInstance();
state.drinkValue = (prefs.getInt('counter') ?? 0);
}
Future<int> loadInitCounter() async {
final prefs = await SharedPreferences.getInstance();
return state.drinkValue = (prefs.getInt('counter') ?? 0);
}
}
and this my cubit state:
class DrinkState {
int drinkValue;
int? amount;
DrinkState({
required this.drinkValue,
});
}
I also tried something like this in my MainPage, how i usually init my state with setState:
#override
void initState() {
super.initState();
BlocProvider.of<DrinkCubit>(context).loadCounter();
}
Context is not accessible in initstate, try using didChangeDependencies life cycle method Flutter get context in initState method
Firstly, I strongly advise you to avoid the StatefulWidget when you use BLoC, but it doesn't mean you can't use it at all. Just be careful because setState() can rebuild BlocProvider inside the stateful widget.
As for the initialization process, I suggest you use this approach on the BlocProvider.
class DrinkScreen extends StatelessWidget {
const DrinkScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DrinkCubit()..loadCounter(), // Or call other initialization method
child: DrinkView(),
);
}
}
This approach works really well if you reuse this screen multiple times, for example, you redirect to DrinkScreen every time you want to fill data and you dispose of the screen afterward (Navigate.pop(), etc). This way you can automatically initialize the cubit every time you redirect into this screen, you don't need to use StatefulWidget to init the cubit.

How to use Flutter Firebase dynamic links with go_router

I had firebase dynamic links integrated and working fine in my app and later i changed to use go_router and now i don't know what i should do to get it working or how to handle it.
The way i imagined it would be is that the FB dynamic link path will be the same as the path to the page in GoRouter routes, and go_router will redirect to the page automatically but i don't think that is how it works and i can't find any resources for this.
So the question is how to use Firebase DynamicLinks with go_router?
this is how I did it, looks like working fine :)
For when the app is in terminated mode:
Future main() async {
...
// Closed state: getInitialLink is Used
final PendingDynamicLinkData? initialLink =
await FirebaseDynamicLinks.instance.getInitialLink();
...
then through runApp(MtApp(initialRoute: initialLink)) pass it to GoRouter.initialLocation field or GoRouter.redirect function, depending on how you use it.
For when the app is in background:
wrap your top widget with a statefulWidget your created like:
class AHDynamicLinksManager extends
StatefulWidget {
final Widget child;
const AHDynamicLinksManager({required this.child, Key? key})
: super(key: key);
#override
State<AHDynamicLinksManager> createState() => _AHDynamicLinksManagerState();
}
class _AHDynamicLinksManagerState extends State<AHDynamicLinksManager> {
#override
void initState() {
super.initState();
FirebaseDynamicLinks.instance.onLink.listen((dynamicLinkData) {
String goingTo = dynamicLinkData.link.path;
GoRouter.of(context).go(goingTo);
}).onError((error) {
GoRouter.of(context).go("/errorpage/$error");
});
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
Hope it helped!

Flutter: How to show a loading page while another page is loading

I'm making a website and I would like to show a loading_page until the home_page is loaded and then transition from one to the other as soon as possible (no fixed timers).
There are multiple ways to do this (ie., using the simplest setState, Streams, multiple packages for state management, just to name a few). I'll give you a simple example just by using a StatefulWidget where you call your API on initState and then navigate when you're done to your new screen.
class LoadingPage extends StatefulWidget {
const LoadingPage({Key? key}) : super(key: key);
#override
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> {
#override
void initState() {
super.initState();
_fetchFromAPI();
}
Future<void> _fetchFromAPI() async {
// Call some API, do anything you want while loading
Navigator.pushReplacementNamed(context, '/home_page');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: const CircularProgressIndicator(),
);
}
}
You can use future builder for this purpose.
Have a look at: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
You can fetch the data as snapshot and use the snapshot.hasdata to check if data is being received or not and till then you can show CircularProgreswIndicator() to show the loading..

using permission handler at main flutter

I am trying to get user permission at the start of the app inside main but i get and exception.
void main() async {
final permission = Permission.manageExternalStorage.request();
if (await permission.isGranted){
}
Error:
Exception has occurred.
_CastError (Null check operator used on a null value)
Check out the code below, I think it can solve your problem, I tested it and worked for me. Instead of getting the permission in main(), it uses the stateful widget MyApp, tries to get the permission in initState(), uses async function _getPermission() to await the user's response and with setState() updates the permission grant state in _permission member.
In the build method you can decide what your app should do if permission is granted or not. In this example I only output a single message depending on the permission. For example you can return your MaterialApp when granted, and a warning message if not, anything you like.
You might be aware of this, but one more thing: if the user permanently denies permission, this solution will not prompt again, so you have to handle this case, if your app cannot work without this, you can offer the user to go into settings and enable it there.
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _permission = false;
void _getPermission() async {
final grant = await Permission.camera.request().isGranted;
setState(() {
_permission = grant;
});
}
#override
void initState() {
_getPermission();
super.initState();
}
#override
Widget build(BuildContext context) {
String message =
_permission ? 'Permission granted' : 'Permission not granted';
return MaterialApp(home: Scaffold(body: Center(child: Text(message))));
}
}
I don't know which package do you use for permission handling, but if you use this, you have to add request() before isGranted:
if (await Permission.manageExternalStorage.request().isGranted) {
}
Or if you want just to get permission status without asking for it:
var status = await Permission.manageExternalStorage.status;
if (status.isDenied) {
}