I am trying to load make a dashboard and now developing the login site. It works that the user gets and email but when I click on the link provided in the email, the "FirebaseAuth.instance.isSignInWithEmailLink($link)" returns false, because $link is "localhost:8080/login" (the current page) instead of the link that has been sent via email.
Here is the FirebaseAuthService code:
class FirebaseAuthService implements AuthService {
FirebaseAuthService() {
_initialize();
}
Future<void> _initialize() async {
/// Set auth persistance for web so user stays signed in
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
print('debug// window.location.href: ' + window.location.href);
print('debug// Uri.base.toString(): ' + Uri.base.toString());
print('debug2// window.localStorage[email]: ' + window.localStorage['email'].toString());
/// idk man...
FirebaseAuth.instance.authStateChanges().listen((User? firebaseUser) {
if (firebaseUser == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
/// Checks if the incoming link is the OTP email link.
// if (FirebaseAuth.instance.isSignInWithEmailLink(Uri.base.toString())) {
if (FirebaseAuth.instance.isSignInWithEmailLink(window.location.href)) {
print('in method debug2// window.location.href: ' + window.location.href);
print('in method debug2// html.window.document.referrer: ' + (window.document as HtmlDocument).referrer);
print('in method debug// Uri.base.toString(): ' + Uri.base.toString());
print('in method debug2// window.localStorage[email]: ' + window.localStorage['email'].toString());
if (kDebugMode) print('Trying to sign in the user with OTP');
try {
await FirebaseAuth.instance
.signInWithEmailLink(
email: window.localStorage['email'] ?? '',
emailLink: window.location.href,
)
.timeout(const Duration(seconds: 10))
.then((value) => print('value: ${value.toString()}'));
} catch (_) {
print('Exceptino.... $_');
}
window.localStorage.remove('email');
if (kDebugMode) print('Successfully signed in the user with OTP');
}
}
#override
bool get isSignedIn => FirebaseAuth.instance.currentUser != null;
#override
Future<void> signOut() async {
await FirebaseAuth.instance.signOut().timeout(const Duration(seconds: 10));
}
}
And here is my main class where FirebaseAuthService is provided (with the provider package):
class VamosEventsDashboard extends StatelessWidget {
VamosEventsDashboard();
final GoRouter _vamosRouter = GoRouter(
debugLogDiagnostics: true,
initialLocation: EventsPage.route,
errorBuilder: (_, __) => const ErrorPage(),
routes: [
GoRoute(path: EventsPage.route, builder: (_, __) => const EventsPage()), // events
GoRoute(path: LoginPage.route, builder: (_, __) => const LoginPage()), // login
],
redirect: (BuildContext context, GoRouterState state) {
return context.watch<AuthService>().isSignedIn ? EventsPage.route : LoginPage.route; // todo change back to events
},
);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Data sources and services
Provider<OrganizationDataSource>(create: (_) => const FirestoreDataSource()),
Provider<AuthService>(create: (_) => FirebaseAuthService()),
],
child: MultiProvider(
providers: [
// View models
ChangeNotifierProvider(
create: (context) => OrganizationViewModel(organizationDataSource: context.read<OrganizationDataSource>()),
),
ChangeNotifierProvider(create: (_) => LoginViewModel()),
],
child: MaterialApp.router(
theme: vamosTheme,
routerConfig: _vamosRouter,
title: 'vamos! Events Dashboard',
),
),
);
}
}
Related
So I am using this answer Prevent unauthenticated users from navigating to a route using a URL in a flutter web app? to make my task. But I always have this error: Error: Could not find the correct Provider<UserProvider> above this Consumer<UserProvider> Widget.
My main.dart:
import '/providers/user_provider.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
return FutureBuilder(
future: _initialization,
builder: (context, appSnapshot) {
return MaterialApp(
//smth
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => NotFoundErrorScreen(),
);
},
routes: {
AuthScreen.routeName: (context) => AuthScreen(),
DashboardScreen.routeName: (context) => DashboardScreen(),
ProductsScreen.routeName: (context) => ProductsScreen(),
PermissionErrorScreen.routeName: (context) => PermissionErrorScreen(),
},
builder: (context, child) {
return Consumer<UserProvider>(
child: DashboardScreen(),
builder: (context, provider, child) {
if (provider.isLoading) {
return SplashScreen();
}
final value = provider.user;
if (value == null) {
return Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
settings: settings, builder: (context) => AuthScreen()),
);
}
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider()),
],
child: child,
);
},
);
},
);
},
);
}
}
My user_provider.dart:
class UserProvider with ChangeNotifier {
bool _isLoading = true;
Map<String, dynamic> _user;
bool get isLoading => _isLoading;
Map<String, dynamic> get user => _user;
getUser() async {
FirebaseAuth.instance.authStateChanges().listen((currentUser) async {
_isLoading = false;
if (currentUser == null) {
_user = null;
notifyListeners();
return;
}
final data = await FirebaseFirestore.instance
.collection('users')
.doc(currentUser.uid)
.snapshots()
.first;
if (data.exists) {
print(data.data());
if (data.data()['role'] == 'admin') {
_user = (data.data());
notifyListeners();
}
return null;
}
});
}
}
This is my first experience with flutter web, so I am confused.
My first thaught was that it does not work, because I don't invoke my getUser() function, however I do not know how to fix it.
Thank you in advance.
The issue is that my app does not navigate to another page automatically when user logs in or out.
class MyApp extends StatelessWidget {
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(home: Wrapper()),
);
}
return Center(child: CircularProgressIndicator());
},
);
}
}
class Wrapper extends StatelessWidget {
const Wrapper({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user != null) {
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => Home(),
'/profile': (context) => Profile()
});
}
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => Welcome(),
'/signup': (context) => SignUp(),
'/signin': (context) => SignIn()
});
}
}
When the app starts it does show the Welcome() page. Then i am able to navigate to the signup page by pressing a signup button as such
onPressed: () {Navigator.pushNamed(context, "/signup");}),
but then when the user signs up, the app doesn't automatically navigate to Home()
class AuthService {
FirebaseAuth auth = FirebaseAuth.instance;
User _userFromFirebaseUser(User user) {
return user != null ? User(id: user.uid) : null;
}
Stream<User> get user {
return auth.authStateChanges().map(_userFromFirebaseUser);
}
Future<String> signUp(email, password) async {
try {
UserCredential user = await auth.createUserWithEmailAndPassword(
email: email, password: password);
await FirebaseFirestore.instance
.collection('users')
.doc(user.user.uid)
.set({'name': email, 'email': email});
_userFromFirebaseUser(user.user);
} on FirebaseAuthException catch (e) {
return e.code;
} catch (e) {
return e;
}
return "";
}
}
I am not sure what the issue is. Any help is appreciated.
First of all you need 1 MaterialApp not 3, then try to debug signUp method maybe there is an erorr for instance signUp returns Future<String> but in catch block you are returning an Exception and finally I suggest you to use Cubit whenever you need to listen state changes to navigate.
When start the app, first work AuthCubit
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthCubit>(
create: (context) => getIt<AuthCubit>()..startApp()),
BlocProvider<CheckRoleCubit>(
create: (context) => getIt<CheckRoleCubit>()),
],
child: MaterialApp(
home: InitialScreen(),
),
);
}
}
which use the simple functions:
void startApp() async {
emit(AuthState.splashScreen());
await Future.delayed(Duration(seconds: 2));
checkAuthRequest();
}
Future<void> checkAuthRequest() async {
final userOption = await firebaseAuthRepository.getUser();
emit(
userOption.fold(
() => AuthState.unauthenticated(),
(_) => AuthState.authenticated(),
),
);
}
in InitialScreen I am using BlocConsumer:
return BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
state.maybeMap(
orElse: () {},
authenticated: (_) {
context.read<CheckRoleCubit>().checkRole();
},
);
},
builder: (context, state) {
return state.maybeMap(
authenticated: (_) => CheckRole(),
unauthenticated: (_) => HomeScreen(),
orElse: () => Container(),
);
},
);
in CheckRoleScreen:
return BlocBuilder<CheckRoleCubit, CheckRoleState>(
builder: (context, state) {
return state.map(
initial: (_) => Container(color: Colors.amberAccent),
admin: (_) => Admin(),
user: (_) => HomeScreen(),
loadFailure: (_) => Container(),
);
},
);
in CheckRoleCubit I am creating a simple function, which fetch userData from Firestore.
StreamSubscription userDataStreamSubscription;
Future<void> checkRole() async {
userDataStreamSubscription = userDataRepository.fetchUserData().listen(
(failureOrFetch) {
emit(
failureOrFetch.fold(
(failure) => CheckRoleState.loadFailure(failure),
(userData) {
if (userData.role == 'admin') {
return CheckRoleState.admin();
} else {
return CheckRoleState.user();
}
},
),
);
},
);
}
Issue is when I am open the app or use restart, after splashScreen, emit initial state of CheckRoleCubit and display container for a second and then display homeScreen or adminScreen. What I am doing wrong?
I know flutter provider is scoped. So I declared providers (those will be needed everywhere) top of MaterialApp. In a screen am chaning a provider value and navigating to another screen. In that screen am not getting the data. Need suggestions and guide where I have done the mistake
main.dart
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<UserAuthViewModel>(
create: (context) => sl<UserAuthViewModel>()),
ChangeNotifierProvider<UserProfileViewModel>(
create: (context) => sl<UserProfileViewModel>()),
ChangeNotifierProvider<BottomNavViewModel>(
create: (context) => sl<BottomNavViewModel>()),
],
child: MaterialApp(
title: "Footsapp",
theme: ThemeData(fontFamily: 'Montserrat'),
debugShowCheckedModeBanner: false,
home: isFirstLaunch == true ? OnBoarding() : SplashView(),
routes: {
//onboarding
SplashView.SCREEN_ID: (context) => SplashView(),
//bottom nav pages
BottomNavContainer.SCREEN_ID: (context) => BottomNavContainer(),
},
),
),
);
splash screen where getting some info from api call
class _SplashViewState extends State<SplashView>
with TokenProvider, AfterLayoutMixin<SplashView> {
final userProfileViewModel = sl<UserProfileViewModel>();
final prefUtil = sl<SharedPrefUtil>();
#override
void afterFirstLayout(BuildContext context) {
Future.delayed(Duration(seconds: 1), () async {
bool isLoggedIn = prefUtil.readBool(IS_LOGGED_IN) ?? false;
bool initialProfileUpdated =
prefUtil.readBool(INITIAL_PROFILE_UPDATED) ?? false;
isLoggedIn == true
? getProfileInfo()
: await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => SocialLoginRegScreen(),
),
(route) => false);
});
}
#override
void initState() {
super.initState();
}
void getProfileInfo() async {
final userProfileResponse = await userProfileViewModel.getUserProfileData();
if (userProfileResponse.success == true) {
print('In splash: ${userProfileViewModel.userProfile.toString()}');
//from log
In splash: {firstName: Ashif, lastName: 123, birthday: 1990-02-03, email:
ashif123#gmail.com, preferredFoot: Left, preferredPosition: Midfielder,
numberOfTimesPlayedWeekly: 4}
Future.delayed(Duration(milliseconds: 500), () async {
await Navigator.pushReplacementNamed(
context,
BottomNavContainer.SCREEN_ID,
);
});
}
}
provider model class
class UserProfileViewModel extends BaseViewModel {
final _profileManageRepo = sl<ProfileManageRepo>();
Profile userProfile = Profile.initial();
Future<UserDataResponse> getUserProfileData() async {
final _userDataResponse = await _profileManageRepo.getUserProfileInfo();
if (_userDataResponse.success == true) {
userProfile = _userDataResponse.profile;
} else {
setState(ViewState.Error);
errorMessage = 'Please try again!';
}
return _userDataResponse;
}
Am trying to get the provider data (user profile) from Profile Screen. But it always get initial value.
class _UserProfileScreenState extends State<UserProfileScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Consumer<UserProfileViewModel>(
builder: (context, viewmodel, child) {
print('In profile: ${viewmodel.userProfile.toString()}');
//but here
In profile: {firstName: , lastName: , birthday: , email: , preferredFoot:
Left, preferredPosition: , numberOfTimesPlayedWeekly: 1(default value)}
return Container(
child: ProfileCardWidget(
profile: viewmodel.userProfile,
),
);
},
);
}
}
I am trying to use local authentication to authenticate the user before he uses the app . But the problem is that I have to use Future Builder for checking user data to go to Home Screen or Login Screen According. Therefore, I have to use local authentication inside Future Builder to authenticate user. But this results in calling fingerprint auth infinite times after I reach to home screen also. So We can't get rid of local auth. Please help and tell if there is another way around . Thanks In Advance :)
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final AuthMethods _authMethods = AuthMethods();
final LocalAuthentication _localAuthentication = LocalAuthentication();
bool _hasFingerPrintSupport = false;
bool _authorizedOrNot = false;
List<BiometricType> _availableBuimetricType = List<BiometricType>();
#override
void initState() {
super.initState();
_getBiometricsSupport();
_getAvailableSupport();
}
Future<void> _getBiometricsSupport() async {
bool hasFingerPrintSupport = false;
try {
hasFingerPrintSupport = await _localAuthentication.canCheckBiometrics;
} catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_hasFingerPrintSupport = hasFingerPrintSupport;
});
}
Future<void> _getAvailableSupport() async {
List<BiometricType> availableBuimetricType = List<BiometricType>();
try {
availableBuimetricType =
await _localAuthentication.getAvailableBiometrics();
} catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_availableBuimetricType = availableBuimetricType;
});
}
Future<void> _authenticateMe() async {
bool authenticated = false;
try {
authenticated = await _localAuthentication.authenticateWithBiometrics(
localizedReason: "Authenticate to use App", // message for dialog
useErrorDialogs: true,// show error in dialog
stickyAuth: false,// native process
);
} catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_authorizedOrNot = authenticated ? true : false;
});
}
#override
Widget build(BuildContext context) {
final themeNotifier = Provider.of<ThemeNotifier>(context);
_authenticateMe();
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => ThemeNotifier(darkTheme),
),
ChangeNotifierProvider(create: (_) => ImageUploadProvider()),
ChangeNotifierProvider(
create: (_) => VideoUploadProvider(),
),
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: MaterialApp(
title: "App",
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/search_screen': (context) => SearchScreen(),
'/setting_page': (context) => settingPage(),
},
theme: themeNotifier.getTheme(),
home: FutureBuilder(
future: _authMethods.getCurrentUser(),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData ) {
return _authorizedOrNot==true ? HomeScreen() : Container();
} else {
return LoginScreen();
}
},
),
),
);
}
}
In this particular case you call _authenticateMe(); at the beginning of your build().
_authenticateMe(); has inside a setState that cause build() to refire again and call _authenticateMe(); thus rebuilding thus rebuilding.
P.S. I would move the FutureBuilder up until is over the MaterialApp, it may cause problem with the use of the hot reload.
Well I figured out a way around by calling the authenticate function in init state and then checking for isauthorizedorNot before returning Future builder .
Here is the code :-
class _MyAppState extends State<MyApp> {
final LocalAuthentication _localAuthentication = LocalAuthentication();
final AuthMethods _authMethods = AuthMethods();
bool _authorizedOrNot ;
Future<void> _authenticateMe() async {
bool authenticated = false;
try {
authenticated = await _localAuthentication.authenticateWithBiometrics(
localizedReason: "Authenticate to use app",
useErrorDialogs: true,
stickyAuth: false,
);
} catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_authorizedOrNot = authenticated ? true : false;
});
}
#override
void initState() {
super.initState();
_authenticateMe();
}
#override
Widget build(BuildContext context) {
final themeNotifier = Provider.of<ThemeNotifier>(context);
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => ThemeNotifier(darkTheme),
),
ChangeNotifierProvider(create: (_) => ImageUploadProvider()),
ChangeNotifierProvider(
create: (_) => VideoUploadProvider(),
),
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: MaterialApp(
title: "App",
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/search_screen': (context) => SearchScreen(),
'/setting_page': (context) => settingPage(),
},
theme: themeNotifier.getTheme(),
home: _authorizedOrNot==true ? FutureBuilder(
future: _authMethods.getCurrentUser(),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
return HomeScreen();
} else {
return LoginScreen();
}
},
) : ( Container(child: Center(child: CircularProgressIndicator()),)
),)
);
}
}