Hello fellow flutter developers,
I have a bug that's been making my life pretty complicated while developing my own app using Flutter. It goes like this:
- User opens app
- if they're not signed in, they're redirected to USP page.
- If they click next, they're redirected to the sign up page.
- Sign-up is provided by Firebase and it's anonymous
- If sign-up is successful, a
Providershould be triggered and a new page is loaded
The bug is that sometimes the user is sent back to the USP page (meaning their user_id is null) despite no exception between thrown during sign-up. If I force the navigation to the signed-in page, then the user doesn't have an user_id and that's an issue for me.
Any one experienced and fixed the same issue? Below you can see how I built my code, maybe this can help?
This is my main file:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SignIn.initializeFirebase();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
getSignInUser() {
return SignIn().user;
}
Widget getMaterialApp() {
return MaterialApp(
title: 'app_title',
home: HomePagePicker(),
onGenerateRoute: RouteGenerator.generateRoute,
debugShowCheckedModeBanner: false,
);
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<MyUser?>.value(value: SignIn().user, initialData: null),
],
child: getMaterialApp(),
);
}
}
class HomePagePicker extends StatefulWidget {
@override
_HomePagePickerState createState() => _HomePagePickerState();
}
class _HomePagePickerState extends State<HomePagePicker> {
@override
Widget build(BuildContext context) {
MyUser? myUser = Provider.of<MyUser?>(context);
if (myUser == null) return IntroScreen(); // this shows the USPs
else {
// this takes you to the signed-in part of the app
return AnotherScreen();
}
}
}
The IntroScreen is a very simple screen with a few USPs and a button to open the registration page. It goes something like
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'login.dart';
class IntroScreen extends StatelessWidget {
static const routeName = '/introScreen';
@override
Widget build(BuildContext context) {
analytics.setScreenName("introScreen");
return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(statusBarColor: ThemeConfig.darkPrimary),
child: Column(...), // show the USPs
floatingActionButton: getFloatingButton(context)
);
}
Widget getFloatingButton(BuildContext buildContext) {
return FloatingActionButton(
backgroundColor: ThemeConfig.primary,
foregroundColor: Colors.white,
child: Icon(Icons.arrow_forward),
onPressed: () {
navigateToScreen(MyLogin.routeName, buildContext, null);
},
);
}
// this is in another file normally but putting it here for completeness
navigateToScreen(String routeName, BuildContext context, Object? arguments) {
Navigator.pushNamed(
context,
routeName,
arguments: arguments
);
}
The important bit in the registration page is this
Future<void> finalizeRegistration(String userName, String userToken) async {
await usersCollection.add({'userName': userName, "userToken": userToken});
}
Future<void> registerUser(String userName) {
return SignIn()
.anonymousSignIn(userName)
.timeout(Duration(seconds: 2))
.then((userToken) {
finalizeRegistration(userName, userToken)
.then((value) => Navigator.pop(context));
})
.catchError((error) {
registrationErrorDialog();
});
}
The SignIn class is the following
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:house_party/models/MyUser.dart';
class SignIn {
final FirebaseAuth _auth = FirebaseAuth.instance;
static Future<FirebaseApp> initializeFirebase() async {
FirebaseApp firebaseApp = await Firebase.initializeApp();
return firebaseApp;
}
Stream<MyUser?> get user {
return _auth
.authStateChanges()
.asyncMap(getUser);
}
Future<String> anonymousSignIn(String userName) async {
var authResult = await _auth.signInAnonymously();
return authResult.user!.uid;
}
Future<MyUser?> getUser(User? user) async {
if (user == null) {
return Future.value(null);
}
return FirebaseFirestore.instance
.collection('users')
.where('userToken', isEqualTo: user.uid)
.get()
.then((res) {
if (res.docs.isNotEmpty) {
return MyUser.fromFireStore(res.docs.first.data());
} else {
return null;
}
});
}
}
Finally, I'm using these versions of firebase
firebase_core: ^1.0.0
cloud_firestore: ^1.0.0
firebase_dynamic_links: ^2.0.0
firebase_auth: 1.1.2
firebase_analytics: ^8.1.1
I hope the problem statement is clear enough!
Thanks in advance!