How to navigate to specific screen page based on the type of user logged in? - flutter

I'm having a mobile app with many screen (sreen A, screen B, screen C ...)
The requirement in my application to include two types of users. One type of user will have access to all screen and the second type user (not loggin) will only have access to Screen A , Screen B. How can I do that ?
My idea is store token after user loggin by SharedPreferences. And check the token is null or not. If not null, user can access all screen. But I don't know where to put this code ? At the main.dart or each screen ?
getToken() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
String token = sharedPreferences.getString("token");
//print(token);
return token;
}
//if token != null { ..can access all Screen } else { ... }

Wrap your home screen in a Futurebuilder. use getToken() as the future for this future builder. Based on the data returned from your function, return the screen you want.

you have two options:
create a global.dart file and save getToken() and also a variable for the storedvalue of token (so only check for the value of sharedPref once) in there. then you can access it as follow:
import './globals.dart' as globals;
globals.accessToken // for getting token value
//or
globals.getToken() // for getting token directly from sharedPref
use state managements like provider

Related

Why device token generated in every run of the flutter application?

I'm using firebase cloud messaging to send notifications to devices. The problem is that the device token regenrated and added to firestore with different id in every run of the application. I want it to be generated juste once for the first installation of the application.
this is my code :
Future init() async {
_firebaseMessaging.getToken().then((token) {
saveTokens(token);
});
}
Future<void> saveTokens(var token) async {
try {
await _firestore.collection('deviceTokens').add({
'token': token,
});
} catch (e) {
print(e);
}
}
this is how I call it in the main():
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await _msgService.init();
// testFirestore();
FirebaseMessaging.onBackgroundMessage(_messageHandler);
this is _messageHandler function:
Future<void> _messageHandler(RemoteMessage message) async {
print(
'background message ${message.notification!.body} + ${message.notification!.title}');
}
Actually token only refresh on one of that cases:
The app deletes Instance ID
The app is restored on a new device
The user uninstalls/reinstall the app
The user clears app data.
So you need to check in your firebase collection if your token (getted on getToken()) is saved yet before add it. If it already exists in your database, don't save it.
For example:
Future<bool> doesTokenAlreadyExist(String token) async {
final QuerySnapshot result = await Firestore.instance
.collection('deviceTokens')
.where('token', isEqualTo: token)
.limit(1)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
return documents.length == 1;
}
The registration token may change when:
The app is restored on a new device
The user uninstalls/reinstall the app
The user clears app data.
More :
Update from Play Store - Token remains same.
When close the application and reopen it - Token remains same.
I recommend you should record that token for the user every time your app launches. Then, you don't face any problems.
(add function to init state of home page of your app)

Access Variable form another class in top level function Flutter

I have a background notification handler in my flutter app, which is a top-level function like so:
Future<void> _onBackgroundMessage(RemoteMessage message) async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final chatClient = StreamChatClient(STREAM_API_KEY);
print(Utils.user?.uid);
print(Utils.user?.streamToken);
chatClient.connectUser(
su.User(id: Utils.user?.uid ?? ''),
Utils.user?.streamToken ?? '',
connectWebSocket: false,
);
NotificationUtils.handleGetStreamNotification(message, chatClient);
SharedPreferences prefs = await SharedPreferences.getInstance();
int appBadgeCounter = prefs.getInt(appBadgePrefsKey) ?? 0;
FlutterAppBadger.updateBadgeCount(appBadgeCounter + 1);
}
In the notification handler, I need to connect to a separate server using a uid and a token. I have a Utils singleton class where I store an app user as a variable. I initialize this Utils app variable in my home page stateful widget class and persist it throughout the app. This way, I can minimize my number of database calls for user data.
However, in this top-level notification handler, the Utils.user is always null. This top level function exists in my home page stateful widget class, but it is a top level function still.
I want to avoid making database calls for every single notification that the app receives. Is there a way to get this Utils. user data without getting null??
you have to setUser before using it in chatClient.connectUser and along with you can check if user is null or not if null initialize it then it stops does not make extra calls.
Future<void> _onBackgroundMessage(RemoteMessage message) async {
...
await Firebase.initializeApp();
final chatClient = StreamChatClient(STREAM_API_KEY);
if(Utils.user != null) {
Utils.setUser();
}
print(Utils.user?.uid);
print(Utils.user?.streamToken);
...
}

I am trying to get user Facebook profile picture inside a drawer in flutter but it get into infinite loop here is the code . Flutter

final facebookUserData =FacebookAuth.instance.getUserData().then((facebookUserData) async {
String profilePicture = facebookUserData['picture']['data']['url'];
textProvider.facebookProfilePhoto(profilePicture);
});
I am using Provider to change the value to a new one that comes from the User data.

Flutter: what's the most performant, seamless way to access SharedPreferences in multiple classes?

So I call
SharedPReference prefs = await SharedPreferences.getInstance();
In my parent Main.dart. Then when the user presses a button in the ButtonBar it gets sent away using
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => Agenda(prefs: prefs,)));
I'm concerned this is not the most robust or performant way to use shared Preferences on child screens. For instance say the user navigated away from Main.dart before I have an instance of SharedPreferences, then the child screen - Agenda.dart - will have null passed into it as the value of prefs.
What happens then? Is the child screen stuck trying to access fields which are null? When Main.dart async process gets an instance of SharedPreferences does it feed it through to the child screen/class?
Would it be more robust to summon SharedPreferences in every class that uses it? Using:
SharedPReference prefs = await SharedPreferences.getInstance();
I feel passing prefs around is better because it reduces bottlenecks, but I don't fully understand.
When I modify values in SharedPreferences in child classes, then BACK button to it's parent screen, will the parent screen be accessing an updated SharedPreference, automagically?
getInstance() will check _completer , because _completer is static
the first time execution will keep reference in SharedPreferences._(this._preferenceCache)
the next time you call getInstance() will always directly return _completer.future; reference of cache, see picture below
no matter where you call getInstance() , even in SecondRoute
Source code of getInstance()
class SharedPreferences {
SharedPreferences._(this._preferenceCache);
static Completer<SharedPreferences> _completer;
...
static Future<SharedPreferences> getInstance() async {
if (_completer == null) {
_completer = Completer<SharedPreferences>();
try {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_completer.complete(SharedPreferences._(preferencesMap));
} on Exception catch (e) {
// If there's an error, explicitly return the future with an error.
// then set the completer to null so we can retry.
_completer.completeError(e);
final Future<SharedPreferences> sharedPrefsFuture = _completer.future;
_completer = null;
return sharedPrefsFuture;
}
}
return _completer.future;
}
What happens then? Is the child screen stuck trying to access fields which are null? When Main.dart async process gets an instance of SharedPreferences does it feed it through to the child screen/class?
This depends on your code. If you await for the SharedPreferences instance and then navigate, it's OK. But if you dont await it, then null will be passed to the next screen and no, it wont be feed through to the child screen/class.
When I modify values in SharedPreferences in child classes, then BACK button to it's parent screen, will the parent screen be accessing an updated SharedPreference, automagically?
Yes, it will, but not automagically. When you pass an object to another class or function in Dart, actually the object itself is not passed. A reference to that object is passed. A reference to an object is like an address to an object's place in memory.
And generally about your question, you can make use of the get_it and injectable packages. get_it is a simple Service Locator for Dart and Flutter projects with some additional goodies and Injectable is a convenient code generator for get_it.
Using these two packages you can register modules, which is a good place for SharedPrefrences. Take a look at this example to learn more about it.
Another way can be making use of an inherited widget. You can provide a value to the whole widget tree, and then you can access that value (that in your case would be SharedPrefrences instance) anywhere in your widgets.

Conditional bottom navigation bar

I have bottom navigation bar which consist of 5 icon which will redirect to 5 screen correspondingly.
The fifth icon is Account screen so when no session it must redirect to Login screen and then load the Account screen. But after login when user tap the Account icon just redirect to Account screen.
How to achieve this? How can I use if condition to determine which layout to load? Error says missing identifier expexted ')'
You can easily achieve this with by passing an async function for the onPressed of Account icon. Your async function would look something like.
Example:
//On Home Page, for account icon pressed
Future<void> checkCredsAndNavigate(){
bool loggedIn = _checkIfLoggedIn(); // this might be a function that gets status of user login, you can fetch from prefs, state, etc.
if(!loggedIn){
var data = await Navigator.of(context).pushNamed('YOUR_LOGIN_ROUTE_NAME');
if(data == null){ // Check for data, will be null when user cancels login
return;
}
}
Navigator.of(context).pushNamed('YOUR_ACCOUNT_ROUTE_NAME');
}
//On Login page,
// On successful login call
Navigator.of(context).pop(data); // this data can be anything like user id, or just a boolean indicating successful login,etc.
//To cancel login, call
Navigator.of(context).pop();
Hope this helps!