Flutter: How to remove all previous routes when auth steam change state? - flutter

I would like to remove all previous routes and return to #WelcomeScreen
in case API response 401
Code
return GetMaterialApp(
scrollBehavior: Behavior(),
defaultTransition: Transition.leftToRight,
translations: LanguageService(),
locale: Get.locale,
fallbackLocale: const Locale('en', 'US'),
debugShowCheckedModeBanner: false,
home: Obx(
() {
if (controller.state is Authenticated) {
return const MainScreen();
} else if (controller.state is UnAuthenticated) {
return WelcomeScreen();
} else if (controller.state is AuthSignIn) {
return SignInScreen();
} else if (controller.state is AuthSignUp) {
return SignUpScreen();
} else {
return const SplashScreen();
}
},
),
theme: AppTheme.light,
darkTheme: AppTheme.dark,
getPages: AppPages.list);
Controller
AuthController auth = Get.find();
Future<void> fetchUsers() async{
var response = await userService.findAll();
//......
if(response.code==401){
auth.authStateStream.value = UnAuthenticated();
Get.back();
Get.back();
}
}
Currently, on state change to UnAuthenticated, it returns to WelcomeScreen
but it does not remove some previous pages. I need to use Get.back one or more depending on the pages I have pushed.
Is there any better solution for this, please give me a suggestion or advice.
Thank you!

You can do :
Get.offAll(()=>WelcomeScreen());

Related

Flutter Locales Using Hive and Riverpod

I am using Hive with app_profile data model to store the app settings in local DB, and using riverpod to call HivedataStore (dependency injection).
The problem is that when I update the hive box of the local type, the app needs to be restarted to take effect, but if I update the Theme to Dark it work as it supposed to.
Here is my code:
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// await AppAssets.preloadSVGs();
final dataStore = HiveDataStore();
await dataStore.init();
await dataStore.createDefaultProfile(
appProfile: AppProfile(
onBoardingCompleted: false,
locale: 'en',
themeMode: ThemeModeCustomized.light,
));
runApp(ProviderScope(overrides: [
hiveDataStoreProvider.overrideWithValue(dataStore),
], child: const MyApp()));
}
hive_data_store.dart
class HiveDataStore {
static const profileBoxName = 'appProfile';
static const themeColor = 'themeColor';
Future<void> init() async {
await Hive.initFlutter();
Hive.registerAdapter<AppProfile>(AppProfileAdapter());
Hive.registerAdapter(ThemeModeCustomizedAdapter());
await Hive.openBox<AppProfile>(profileBoxName);
}
Future<void> createDefaultProfile({
required AppProfile appProfile,
}) async {
final box = Hive.box<AppProfile>(profileBoxName);
if (box.isEmpty) {
await box.put('0', appProfile);
} else {
print('box already have these Values : ${box.get(0)?.locale}');
}
}
Future<void> updateBox({
bool? onBoardingCompleted,
String? locale,
ThemeModeCustomized? themeMode,
}) async {
final box = Hive.box<AppProfile>(profileBoxName);
final userProfile = box.get('0');
final newProfile = userProfile!.copyWith(
onBoardingCompleted: onBoardingCompleted,
locale: locale,
themeMode: themeMode);
await box.put('0', newProfile);
}
AppProfile? appProfile() {
final box = Hive.box<AppProfile>(profileBoxName);
return box.get(0);
}
ValueListenable<Box<AppProfile>> appProfileListenable() {
return Hive.box<AppProfile>(profileBoxName).listenable();
}
}
final hiveDataStoreProvider = Provider<HiveDataStore>((ref) {
throw UnimplementedError();
});
myapp.dart
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(hiveDataStoreProvider);
return ValueListenableBuilder(
valueListenable: provider.appProfileListenable(),
builder: (context, Box<AppProfile> box, __) {
print('myapp rebuilds listenablebuilder');
final appProfile = box.get('0');
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppAssets.getLocals(appProfile!),
onGenerateTitle: (context) {
var t = AppLocalizations.of(context);
return t!.appTitle;
},
themeMode: AppAssets.themeModeGeter(appProfile),
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
initialRoute: '/',
routes: {
'/': (context) {
return const HomePage();
}
},
);
});
}
}
homepage.dart
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
print('homepage rebuils');
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.appTitle),
),
body: Center(
child: Consumer(builder: (context, WidgetRef ref, __) {
return Column(
children: [
TextButton(
onPressed: () {
ref.read(hiveDataStoreProvider).updateBox(
locale: 'ar', themeMode: ThemeModeCustomized.dark
);
},
child: const Text('العربية')),
TextButton(
onPressed: () {
ref.read(hiveDataStoreProvider).updateBox(
locale: 'en', themeMode: ThemeModeCustomized.light
);
},
child: const Text('English')),
],
);
}),
),
);
}
}
I think something need to be Changed in MyApp class, I use ValueListenableBuilder because I've seen in it in the Hive official package page.
Why the app need to be restarted to take effect for the locales? Unlike the app Theme which works perfectly.
Here my app_assets.dart code (just extra info):
class AppAssets {
static List<Locale> getLocals(AppProfile appProfile) {
switch (appProfile.locale) {
case 'ar':
return [const Locale('ar', '')];
case 'en':
return [const Locale('en', '')];
case '':
return AppLocalizations.supportedLocales;
default:
return AppLocalizations.supportedLocales;
}
}
static ThemeMode themeModeGeter(AppProfile? appProfile) {
switch (appProfile?.themeMode.name) {
case 'dark':
{
return ThemeMode.dark;
}
case 'light':
{
return ThemeMode.light;
}
case 'system':
{
return ThemeMode.system;
}
default:
ThemeMode.system;
}
return ThemeMode.system;
}
}
my mistake is that I was updating supportedLocales without updating locale in the MaterialApp Widget , I will keep the question though it might have a hint for others ..
supportedLocales: AppAssets.getLocals(appProfile),
locale: Locale('$appProfile.locale',''),

Flutter MaterialApp home based on futureBuilder

When my app starts up, it detects whether the user is logged in or not (with firebase) and based on this check, it shows the homepage or the login page. Up to now everything is fine, but I would like to add one more layer.
The user can login as normal user or as admin.
So the check should be not only on the authentication state, but also on the "level" of the user, and show different pages, based on the user level.
I get the user level with a query on the firestore database, so it's a Future.
This is the code i'm using:
final usersCollection = FirebaseFirestore.instance.collection('users');
User loggedUser = FirebaseAuth.instance.currentUser;
Future<InfoUtente> userInfo;
String livelloLogin;
// here I get the user from the firestore database, based on the authenticated user id
Future<InfoUtente> fetchInfoUtente() async {
final response = await usersCollection
.where(
'uid',
isEqualTo: loggedUser.uid,
)
.get();
return InfoUtente.fromFireStore(response.docs.first);
}
// here I return the page based on the user authentication "level"
Future<Widget> widgetChoice() async {
if (!isLogged)
return LoginNewPage();
else {
userInfo.then(
(value) {
livelloLogin = value.loginLevel;
if (livelloLogin == 'struttura')
return StrutturaPage();
else
return MainPage();
},
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// the homepage of the material app is a future builder
home: FutureBuilder(
future: widgetChoice(),
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if (!widget.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return widget.data;
},
),
);
}
something is not right because it always shows the circular progress indicator.
What am I doing wrong?
Is this the correct way of doing this or am I completely wrong?
If there is no data fetched or found, your screen will stuck on loading infinitely. Update your builder's implementation as
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if(widget.connectionState == ConnectionState.done){
if (!widget.hasData) {
return Center(
child: Text('No Data exists')
);
}
return widget.data;
}
return Center(
child: CircularProgressIndicator(),
);
},
And update your widgetChoice as
Future<Widget> widgetChoice() async {
if (!isLogged)
return LoginNewPage();
else {
var userInfo = await fetchInfoUtente();
livelloLogin = userInfo.loginLevel;
if (livelloLogin == 'struttura')
return StrutturaPage();
else
return MainPage();
}
}
If i'm right you have to call the future function like that:
FutureBuilder(
future: widgetChoice,
Without ()

Internet connectivity in flutter

How to implement continuous Internet connectivity check in flutter only once for whole app, I have almost complete app, now I need to add Internet connectivity check, Please help, thanks in advance
#override
Widget build(BuildContext context) {
return StreamBuilder<ConnectivityResult>(
stream: connectivityStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final connectivityResult = snapshot.data;
if (connectivityResult == ConnectivityResult.none) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: NoInternetConnectionScreen(),
);
}
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen(),
routes: routes,
);
} else if (snapshot.hasError) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: NoInternetConnectionScreen(),
);
}
return Center(child: CircularProgressIndicator());
}
);
}
The connectivity plugin states in its docs that it only provides information if there is a network connection, but not if the network is connected to the Internet
Note that on Android, this does not guarantee connection to Internet.
For instance, the app might have wifi access but it might be a VPN or
a hotel WiFi with no access.
You can use
import 'dart:io';
...
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
}
} on SocketException catch (_) {
print('not connected');
}
I think that the best practice would be using connectivity plugin and wrapping your app in a stream builder
https://pub.dev/packages/connectivity
Your main screen / main page should be something like this:
class MainScreen extends StatelessWidget {
Stream connectivityStream = Connectivity().onConnectivityChanged;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppStyle.backgroundColor,
body: StreamBuilder<ConnectivityResult>(
stream: connectivityStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final connectivityResult = snapshot.data;
if (connectivityResult == ConnectivityResult.none) {
return NoConnectionPage();
}
return HomePage();
} else if (snapshot.hasError) {
return NoConnectionPage();
// or some error page, but I think no connection page is what you
// want here
}
return Center(child: CircularProgressIndicator());
}
)
);
}
}
In NoConnectionPage() you can have a button that retries the connection with a method like this:
void _retryConnection() async {
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
Navigator.of(context).pop(); // going back to MainScreen()
}
} on SocketException catch (_) {
print('not connected');
}
}
Simply use the internet_connectivity_checker package like this :
class Hello extends StatelessWidget {
const Hello({super.key});
#override
Widget build(BuildContext context) {
return internetConnectivityBuilder(
(status) {
bool connected = status == ConnectivityStatus.online;
return Text(connected ? "Online" : "Offline");
},
);
}
}
Get more info about this package here.

Navigate to Home screen after receiving token in Flutter

I'm trying to implement user authentication.
To log user in I created a Provider called Auth:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import '../../utilities/http_exception.dart';
class Auth extends ChangeNotifier {
String _userId;
String _userUUID;
String _token;
DateTime _expiryDate;
bool get isAuth {
return token != null;
}
String get token {
if (_token != null) {
return _token;
}
return null;
}
Future<void> login(String email, String password) async {
const String url = "http://10.0.2.2:8000/users/api/token/";
try {
final response = await http.post(
url,
headers: {"Content-Type": "application/json"},
body: json.encode(
{
"email": email,
"password": password,
},
),
);
final responseData = json.decode(response.body);
if (response.statusCode == 200 && responseData["token"] != null) {
_token = responseData["token"];
_userUUID = responseData["user"]["uuid"];
notifyListeners();
} else {
print("SOMETHING WENT WRONG HERE");
}
} catch (e) {
throw e;
}
}
}
As you can see I called notifyListeners() to notify listeners to do some operations.
What kind of operations?
I used a Consumer around my MateriaApp widget and for home parameter I've checked weather the token is null or not.
if it's null OK, load GeneralAuth screen otherwise load Home screen.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return OrientationBuilder(
builder: (context, orientation) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: Auth()),
ChangeNotifierProvider.value(value: Users()),
ChangeNotifierProvider.value(value: Teachers()),
],
child: Consumer<Auth>(
builder: (context, auth, _) => MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [Locale("fa", "IR")],
onGenerateRoute: Router.generateRoute,
initialRoute: GeneralAuthRoute,
onUnknownRoute: (settings) => MaterialPageRoute(
builder: (context) => Undefined(name: settings.name),
),
locale: Locale("fa", "IR"),
theme: ThemeData(
fontFamily: "IranSans",
primaryColor: Color(0xFF212121),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.black.withOpacity(0),
),
),
home: auth.isAuth ? Home() : GeneralAuth(),
),
),
);
},
);
},
);
}
}
Looks like everything is OK because as I said I called notifyListeners() but after receiving token even though I have token and auth.isAuth returns true, screen won't load up.
What is the problem?
Home property is something that is not dynamically changing your screen, and this behavior is expected. What you need to do is you need to follow through logic where you wait for login endpoint to execute, and based on response you get, you would need to decide on the screen that gets presented.
Example of that would be this:
await login('a#a.com', '12345');
if (Auth.isAuth) {
// Present here Home screen
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Home()),
);
}

FutureBuilder in flutter building app again and again?

what I am trying to do is check if user is logged in already or not. there is a futurebuilder inside consumer which is notifying listeners. check code:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Auth(),
)
],
child: Consumer<Auth>(
builder: (ctx, auth, _) {
print('making it again and again');
return MaterialApp(
title: 'MY app',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.deepOrange,
),
home: auth.isAuth
? FirstScreen()
: FutureBuilder(
future: auth.tryAutoLogin(), //inside this function there is notifylisteners()
builder: (ctx, authResultSnapShot) =>
authResultSnapShot.connectionState ==
ConnectionState.waiting
? SplashScreen()
: AuthScreen(),
));
},
),
);
}
What I get:
this is rebuilding the app again and again. tryautologin is called repeatedly.
What I want:
check for is a user logged in only once the app is started.
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData = json.decode(prefs.getString('userData'));
final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
_accessToken = extractedUserData['accessToken'];
_refreshToken = extractedUserData['refreshToken'];
print(extractedUserData['userId']);
_userId = extractedUserData['userId'];
if (expiryDate.isBefore(DateTime.now())) { //for now this if statement is not running
try {
await _getAccessToken();
} catch (error) {
return false;
}
}
print('tryautologin');
notifyListeners();
_autoGetAccessToken(); //this function doesn't have any notifylisteners
return true;
}
edited:
String get accessToken {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_accessToken != null) {
return _accessToken;
}
return null;
}
bool get isAuth {
print(accessToken != null);
return accessToken != null;
}
Even the question is old quite a bit of time, this answer will help another. I have faced the problem also. this happens when you use a future builder inside a consumer. it is rebuilding again and again infinitely. So, instead of the future builder try using an if-else statement.
child: Consumer<Auth>(
builder: (ctx, auth, _) {
//Sizedbox() used for nullsafety
Widget child = SizedBox();
if (auth.isAuth) {
child = FirstScreen();
} else if (!auth.triedAutoLogin) {
auth.tryAutoLogin();
child = SplashScreen();
} else {
child = AuthScreen();
}
return MaterialApp(
title: 'MY app',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.deepOrange,
),
home: child,
);
},
),
inside Auth class keep a boolean variable like triedAutoLogin to keep track.