I have using uni_links: ^0.5.1 & get: ^4.3.4 for deep linking.
main.dart router setup :
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Navigator 2.0 Deep Link',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routerDelegate: routerDelegate,
routeInformationParser: const MyRouteInformationParser(),
);
below is route details :
MaterialPage _createPage(RouteSettings routeSettings) {
Widget child;
switch (routeSettings.name) {
case '/':
child = const HomePage();
break;
case '/college':
child = College(arguments: routeSettings.arguments as Map<String,dynamic>);
break;
case '/hostel':
child = Hostel();
break;
case '/stadium':
child = Stadium();
break;
default:
child = errorWidget();
}
return MaterialPage(
child: child,
key: Key(routeSettings.toString()) as LocalKey,
name: routeSettings.name,
arguments: routeSettings.arguments,
);
}
After navigating I can able to receive data from parameter and when user refresh the screen i can able to restore param data using below codes.
#override
RouteInformation restoreRouteInformation(List<RouteSettings> configuration) {
final location = configuration.last.name;
final arguments = _restoreArguments(configuration.last);
return RouteInformation(location: '$location$arguments');
}
String _restoreArguments(RouteSettings routeSettings) {
if(routeSettings.name == '/college') {
return '?id=${(routeSettings.arguments as Map)['id'].toString()}';
} else {
return '';
}
}
Everything fine and works.
my question is I have feeling like data leaking because of passing data from param. so is there any other way to hold the data when user refresh the screen without getting from Param or local storage.
http://localhost:64042/#/college?id=7
thanks...
Related
In my flutter app there are two kinds of users, Admin and Customer. I wish to implement a functionality which will navigate The Customer to CustomerHomePage() and Admin to AdminHomePage().
I have wrapped my home property of MaterialApp with StreamBuilder which should to listen to any added values to the currentUserStream and alter the UI accordingly :
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// home: const MyHomePage(title: 'Flutter Demo Home Page'),
home: Scaffold(
body: StreamBuilder(
initialData: null,
stream: FirestoreServices.currentUserStream,
builder: (context, snapshot) {
Widget widget = LogInPage();
if (snapshot.data != null) {
// Go to AdminHomePage if the logged in User is a Admin
print("Logged in Usertype : ${snapshot.data!.userType.toString()}");
if (snapshot.data!.userType == UserType.admin) {
widget = AdminHomePage(caUser: snapshot.data!);
}
// Go to CustomerHomePage if the logged in User is a Customer
else if (snapshot.data?.userType == UserType.customer) {
widget = CustomerHomePage(
caUser: snapshot.data!,
);
}
} else {
widget = LogInPage();
}
return widget;
}),
));
}
}+
the Stream so used in this streamBuilder is is a static property of FirestoreServices Class which is made the following way :
static Stream<CAUser> get currentUserStream async*{
FirebaseAuth.instance.authStateChanges().map(
(event) async* {
yield await FirestoreServices().uidToCAUser(event!.uid);
});
}
According to me the problem that's occuring is the values are either not getting added to the stream or they aren't getting read by the StreamBuilder. The effect of this is that the screen isn't navigationg to any of the HomePages
I tried the code which I just posted above, and I expect there's something wrong with the getter function.
type here
My Flutter project is migrating to go_router and I have trouble understanding how to access riverpod providers in either a GoRoute's build method or redirect method, as I need to access the user's data to control the navigation.
I have a top-level redirect that checks if a user is logged in and sends them to a LoginPage if not. All users can access so-called activities in my app, but only admins can edit them. Whether a user is an admin is stored in a riverpod userDataProvider, which always contains a user if the user is logged in. Now if a user attempts to enter the route /:activityId?edit=true, I want to check whether they are allowed to by accessing the userDataProvider. However, I do not see what the clean way of accessing this provider is.
I found somewhere (can't find the thread anymore), that one way is to use ProviderScope.containerOf(context).read(userDataProvider), but I have never seen this before and it seems a bit exotic to me. Is this the way to go?
My GoRoute looks something like this:
GoRoute(
path: RouteName.event.relPath,
builder: (context, state) {
final String? id = state.params['id'];
final bool edit = state.queryParams['edit'] == 'true';
if (state.extra == null) {
// TODO: Fetch data
}
final data = state.extra! as Pair<ActivityData, CachedNetworkImage?>;
if (edit) {
return CreateActivity(
isEdit: true,
data: data.a,
banner: data.b,
);
}
return ActivityPage(
id: id!,
data: data.a,
banner: data.b,
);
},
redirect: (context, state) {
final bool edit = state.queryParams['edit'] == 'true';
if (edit) {
// IMPORTANT: How to access the ref here?
final bool isAdmin =
ref.read(userDataProvider).currentUser.customClaims.admin;
if (isAdmin) {
return state.location; // Includes the queryParam edit
} else {
return state.subloc; // Does not include queryParam
}
} else {
return state.path;
}
},
),
In my current application, I used something similar approach like this :
Provider registration part (providers.dart) :
final routerProvider = Provider<GoRouter>((ref) {
final router = RouterNotifier(ref);
return GoRouter(
debugLogDiagnostics: true,
refreshListenable: router,
redirect: (context, state) {
router._redirectLogic(state);
return null;
},
routes: ref.read(routesProvider));
});
class RouterNotifier extends ChangeNotifier {
final Ref _ref;
RouterNotifier(this._ref) {
_ref.listen<AuthState>(authNotifierProvider, (_, __) => notifyListeners());
}
String? _redirectLogic(GoRouterState state) {
final loginState = _ref.watch(authNotifierProvider);
final areWeLoggingIn = state.location == '/login';
if (loginState.state != AuthenticationState.authenticated) {
return areWeLoggingIn ? null : '/login';
}
if (areWeLoggingIn) return '/welcome';
return null;
}
}
Main app building as router (app.dart):
class App extends ConsumerWidget {
const App({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context, WidgetRef ref) {
final GoRouter router = ref.watch(routerProvider);
return MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
debugShowCheckedModeBanner: false,
title: 'Flutter Auth',
}
}
}
And as entrypoint (main.dart):
Future<void> main() async {
F.appFlavor = Flavor.dev;
WidgetsFlutterBinding.ensureInitialized();
await setup();
runApp(ProviderScope(
observers: [
Observers(),
],
child: const App(),
));
}
All examples that I've found are using "navigatorObservers" from the MaterialApp constructor
static FirebaseAnalytics analytics = FirebaseAnalytics.instance;
static FirebaseAnalyticsObserver observer =
FirebaseAnalyticsObserver(analytics: analytics);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase Analytics Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
navigatorObservers: <NavigatorObserver>[observer],
home: MyHomePage(
title: 'Firebase Analytics Demo',
analytics: analytics,
observer: observer,
),
);
}
but my app uses MatterialApp.router from the Navigator 2.0 pattern and could not find an equivalent for attaching an navigatorObserver in order to track screen change events for firebase analytics. Any workarounds or suggestions on this?
The MaterialApp.router constructor has required routerDelegate property. This delegate is usually a wrapper of the Navigator widget. This widget has observers property - that is exactly what you are looking for.
Here is an example of the RouterDelegate, which registers both Firebase and Segment observers:
class AppNavigator extends RouterDelegate<void>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<void> {
AppNavigator({
#required Page<void> initialPage,
this.analyticsObserver,
this.segmentObserver
}) : assert(initialPage != null),
navigatorKey = GlobalKey<NavigatorState>() {
_pagesStack = [initialPage];
}
final FirebaseAnalyticsObserver analyticsObserver;
final SegmentObserver segmentObserver;
...
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [..._pagesStack],
observers: [analyticsObserver, segmentObserver],
onPopPage: (route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
for (final page in _pagesStack) {
if (page == route.settings) {
_pagesStack.remove(page);
notifyListeners();
break;
}
}
return true;
},
);
}
}
Note, that under the hood by default the Firebase Analytics module expects your page routes to have a name property set as a part of RouteSettings:
// From FirebaseAnalyticsObserver
void _sendScreenView(PageRoute<dynamic> route) {
final String? screenName = nameExtractor(route.settings);
if (screenName != null) {
analytics.setCurrentScreen(screenName: screenName).catchError(
(Object error) {
final _onError = this._onError;
if (_onError == null) {
debugPrint('$FirebaseAnalyticsObserver: $error');
} else {
_onError(error as PlatformException);
}
},
test: (Object error) => error is PlatformException,
);
}
}
You can override this behavior by providing custom nameExtractor property to the FirebaseAnalyticsObserver constructor.
I am new to developing in flutter, and I am trying to make an app that will redirect the user to a different screen depending on what role they are as a user. I technically need to implement this in two places, in my the build of my main, and when the log in button is pressed.
What I have been trying to do right now is in the main, check if the user is logged in, then get his user uid, then use that uid to query the database for his role. It seems like a very crude solution as the application feels very choppy + its crashing and it takes it a while at boot to redirect to the right page and I'm unsure if I should be using real time database instead of the normal one for this. Any pointers on how to optimize this would be greatly appreciated.
class MyApp extends StatelessWidget {
late String userRole;
late String path;
#override
Widget build(BuildContext context) {
String userUid = FirebaseAuth.instance.currentUser!.uid;
userRole = "uprav";
path = "";
if(FirebaseAuth.instance.currentUser!.uid.isNotEmpty)
{
Database.setUserUid(userUid);
Database.getRole().then((value) {
userRole = value;
});
switch(userRole) {
case "uprav":
path = "/repair-flow";
break;
case "majs":
path = "/majstor-flow";
break;
case "pred":
path = "/repair-flow";
break;
}
}
print(userRole + " this is .then");
return ScreenUtilInit(
designSize: Size(375, 812),
builder: () => MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Majstor',
theme: ThemeData(
primaryColor: Constants.primaryColor,
scaffoldBackgroundColor: Color.fromRGBO(255, 255, 255, 1),
visualDensity: VisualDensity.adaptivePlatformDensity,
textTheme: GoogleFonts.openSansTextTheme(),
),
initialRoute: FirebaseAuth.instance.currentUser == null ? "/" : path,
onGenerateRoute: _onGenerateRoute,
),
);
}
}
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
switch (settings.name) {
case "/":
return MaterialPageRoute(builder: (BuildContext context) {
return Home();
});
case "/repair-flow":
return MaterialPageRoute(builder: (BuildContext context) {
return RequestServiceFlow();
});
case "/majstor-flow":
return MaterialPageRoute(builder: (BuildContext context){
return MajstorServiceFlow();
});
default:
return MaterialPageRoute(builder: (BuildContext context) {
return Home();
});
}
}
In flutter, we use need to manage the states of screens.
In your case,
State 1: Loading data - Show loading widget
State 2: Loaded data, you need to update layout - Show the screen based on the role.
There are many ways to do so:
Future builder (https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)
BLOC(this is a bit hard for beginner but worth in the future development).
I want to insert query parameters to a named route.
I have this code on my MaterialApp
Widget build(BuildContext context) {
return MaterialApp(
title: 'Web',
theme: ThemeData(
primarySwatch: Colors.amber,
),
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/login',
routes: {
'/login': (context) => LoginPage(),
'/mainmenu': (context) => MainMenu(),
},
);
}
Now I want to insert query parameters (for example id) to '/mainmenu' so when I want to navigate to the main menu page, the URL becomes for example: http://localhost:57430/#/mainmenu/?id=1234. Is there any way to do that? Thanks
You can pass Data through Navigator in Flutter by,
Navigator.pushReplacementNamed(context, '/home', arguments: {
'id': 1234
});
In the above code you will be pass data as a map to the next screen using arguments.
You can decode the map by these steps:
Declaring a Map variable in the next screen:
Map data = {}
Then decoding it by:
data = ModalRoute.of(context).settings.arguments;
print(data);
It's recommended to create a class to specify the arguments that need to be passed to the route, for example:
class MainMenuArguments {
final int id;
MainMenuArguments(this.id);
}
That can be passed to a Navigator:
Navigator.pushNamed(context, MainMenuScreen.routeName, arguments: MainMenuArguments(1234)); // id = 1234
And can be then accessed from the MainMenu Widget:
class MainMenuScreen extends StatelessWidget {
static const routeName = '/mainMenu';
#override
Widget build(BuildContext context) {
final MainMenuArguments args = ModalRoute.of(context).settings.arguments;
return Scaffold(
body: Center(
child: Text(args.id.toString()), // displays 1234
),
);
}
}
In order to do so, you'd need to register the route inside the MaterialApp constructor:
MaterialApp(
routes: {
MainMenuArgumentsScreen.routeName: (context) => MainMenuArgumentsScreen(),
},
);
Flutter has a cookbook specially for this situation. Link here