authStateChanges only work if in debug mode - flutter

I have a flutter app and everything was fine until i want to release it.
I'm using firebase for auth.
I'm using:
firebase_core: ^0.7.0
firebase_auth: ^0.20.0
In debug mode or in release, my firebase auth login works fine. My problem is after that.
I have a decentralized 'listener' to firebaseAuth.authStateChanges. Here is where i control my app authentication. This is my buildSelf$ function in my auth repository(where i build the auth state listener):
ReplayConnectableStream<AuthState> buildSelf$() {
return Rx.concat([
Stream.value(AuthInit()),
firebaseAuth.authStateChanges().switchMap((firebaseUser) {
print('[AuthRepository][buildSelf][authStateChanges] firebaseUser');
print(firebaseUser);
if (firebaseUser == null) {
return Stream.value(Unauthenticated());
}
/* continue function */
return Authenticated(firebaseUser: firebaseUser);
})
]).publishReplay();
}
buildSelf$ is a method for my AuthRepository. And i initialize it on:
AuthRepository._() {
Firebase.initializeApp().then((app) {
_firebaseAuth = FirebaseAuth.instance;
state$ = buildSelf$();
state$.connect();
});
setPackageInfo();
}
static final AuthRepository instance = AuthRepository._();
All this code is inside my AuthRepository.
The problem is:
When i'm running my app in debug mode. Every thing works fine. I can login and my app (my navigator observer uses the auth repository state$) and i'm redirected to home page. [Here a printscreen from terminal in debug mode. success authStateChanges emit
But when i'm running im release mode, my login response shows in my terminal 'success' but my listener does not show state changes. Here a printscreen from terminal in release mode. authStateChanges emit only 1 time(when opening)
I'm realy lost about whats going on. I tried even to call this authStateChanges directly in my app.dart but still the same way(in release, only checking auth state once).
Solution:
After 3 days of headache, i finally found out my problem:
My app does not initialize firebase app on root (main). So i have to initialize it every time i need to use some firebase package.
I believe that every time one app was initialized, the others failed.
I move my firebase.initializeApp() to my main function and remove every other firebase initialize. Then, everything works fine.
This is my main:
void main() async {
/* ... main code ... */
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(App());
}

I did implement a project with the presented methods in release mode in my iPhone but wasn't able to reproduce the problem, the implementation I did to reproduce the project was:
class _TestFirebaseState extends State<TestFirebase> {
bool userLogado = false;
_loginLogout() {
if (FirebaseAuth.instance.currentUser == null) {
FirebaseAuth.instance.signInWithEmailAndPassword(
email: 'teste1#teste.com', password: '123456');
} else {
FirebaseAuth.instance.signOut();
}
}
#override
void initState() {
super.initState();
final stream = Rx.concat(
[
Stream.value('começou'),
FirebaseAuth.instance.authStateChanges().switchMap<String>(
(user) {
debugPrint('user: ' + user.toString());
if (user == null) {
return Stream.value('unauth');
} else {
return Stream.value('auth');
}
},
),
],
).publishReplay();
stream.listen((value) {
debugPrint('value: ' + value);
this.setState(() {
this.userLogado = value == 'auth';
});
});
stream.connect();
}
#override
Widget build(BuildContext context) {
String text = "sair";
if (!userLogado) {
text = "entrar";
}
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
child: Center(
child: RaisedButton(
onPressed: _loginLogout,
child: Text(text),
),
),
),
);
}
}
the only detail is that the main component that loads everything in the app has a future builder that awaits the firebase to init:
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder(
// Initialize FlutterFire:
future: Firebase.initializeApp(),
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text(
'ocorreu um problema inicializando o aplicativo...',
),
),
);
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
// return LoginPage();
// return VerifySignIn(homePage: Home());
return TestFirebase();
}
// Otherwise, show something whilst waiting for initialization to complete
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
),
debugShowCheckedModeBanner: false,
);
}
And another detail is that the firebase configuration is implemented following the flutter tutorial for the platform used.
So in order to evaluate if the app configuration is just fine, I would implement that button app inside the same project so you can evaluate it properly.

Related

How to navigate to the Home Page after a successful Login using Flutter (Dart)

I have a Login Controller that does the action of loging in (with Google)
class LoginController extends GetxController {
final _googleSignin = GoogleSignIn();
var googleAccount = Rx<GoogleSignInAccount?>(null);
login() async {
googleAccount.value = await _googleSignin.signIn();
}
logout() async {
googleAccount.value = await _googleSignin.signOut();
}
}
Then I have a Log In UI that shows everything
final controller = Get.put(LoginController());
LoginIn({
super.key,
required String title,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign-In Page')),
body: Center(
child: Obx(() {
if (controller.googleAccount.value == null) {
return loginButton(); //the Login button to log in with google
} else {
return seeProfile(); //after Login you are then able to see your profile
}
}),
));
}
And then ofcourse I have a HomePage
How do I go to the Home Page after a successful log in
I tried to use ChatGPT and I did watch a couple of Youtube Videos but their code is ofcourse different then my and that makes it hard to implement their solution
A cleaner method would be to use Stream builder and subscribe to the auth stream in the main.dart file. Based on the value, navigate the user.
'''
StreamBuilder<User?>(
initialData: null,
// stream: RepositoryProvider.of(context).authStream(),
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData) {
return HomePage();
}
return Container();
});
I think checking the example in the google_sign_in's package on pub.dev might help you achieving what you want.
In summary, I would suggest you to listen to changes to the user in the initState method and push a new page after signIn is successful:
#override
void initState() {
super.initState();
StreamSubscription<GoogleSignInAccount?> subscription = _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) {
if (account != null) {
//push new screen
Navigator.of(context).popAndPushNamed(...)
subscription.cancel()
}
});
}
I couldn't test this code, but it should give you a general idea on how to proceed. Again, check the link above for more information.

Add check internet connection bloc to another screen flutter

I've 100% finished App, The thing is I wanna implement check internet connection on every single screen, I already created a function inside a cubit "Internet Cubit" which does exactly what I want.
What I wanna know is how to call this function on every screen and check for the state if it's "not Connected' it should Navigate to another screen
and by the way every screen is wrapped with a bloc Consumer of a different cubit than my "internet Cubit"
here's my "internet cubit" and for example here's my my "Home Screen "
Internet cubit
class InternetCubit extends Cubit<InternetState> {
InternetCubit() : super(InternetInitial());
StreamSubscription? subscription;
void checkConnection() {
subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
if (result == ConnectivityResult.wifi || result == ConnectivityResult.mobile) {
emit(ConnectedState());
} else {
emit(NotConnectedState());
}
});
}
#override
Future<void> close() {
subscription!.cancel();
return super.close();
}
}
home Screen
body: BlocConsumer<HomeCubit, HomeState>(
listener: (context, state) {},
builder: (context, state) {
if (state.homeState == RequestState.loading) {
return Center(
child: CircularProgressIndicator(color: AppColors.primaryColor),
);
}
if (state.homeState == RequestState.loaded) {
return Column(
children: [
SizedBox(height: 20.h),

Flutter: Image.memory() fails with bytes != null': is not true -- when there IS data

I'm trying to display an image based on (base64) data coming from a backend, but I keep getting the error bytes != null': is not true.
Here's my code:
class _FuncState extends State<Func> {
Uint8List userIconData;
void initState() {
super.initState();
updateUI();
}
void updateUI() async {
await getUserIconData(1, 2, 3).then((value) => userIconData = value);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
Container(
child: CircleAvatar(
backgroundImage: Image.memory(userIconData).image, // <--- problem here
maxRadius: 20,
),
),
),
);
}
}
Helper code:
Future<Uint8List> getUserIconData(
role,
id,
session,
) async {
var url = Uri.https(kMobileAppAPIURL, kMobileAppAPIFolder);
var response = await http.post(url, body: {
'method': 'getUserProfilePic',
'a': id.toString(),
'b': role.toString(),
'c': session.toString(),
});
if (response.statusCode == 200) {
Map data = jsonDecode(response.body);
return base64Decode(data['img']);
}
return null;
}
I have stepped through the code with a debugger and I have confirmed the helper function is returning the correct series of bytes for the image.
I'd appreciate any pointers.
Further note. The error also says:
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub
This is quite simple; if you take a look at your code you should be able to follow through this sequence of operations.
The widget is created. No action. At this point userIconData is null.
initState is called. async http call is initiated. userIconData == null
build is called. build occurs, throws error. userIconData == null
http call returns. userIconData is set. userIconData == your image
Due to not calling setState, your build function won't run again. If you did, this would happen (but you'd still have had the exception earlier).
build is called. userIconData is set. userIconData == your image
The key here is understanding that asynchronous calls (anything that returns a future and optionally uses async and await) do not return immediately, but rather at some later point, and that you can't rely on them having set what you need in the meantime. If you had previously tried doing this with an image loaded from disk and it worked, that's only because flutter does some tricks that are only possible because loading from disk is synchronous.
Here are two options for how you can write your code instead.
class _FuncState extends State<Func> {
Uint8List? userIconData;
// if you're using any data from the `func` widget, use this instead
// of initState in case the widget changes.
// You could also check the old vs new and if there has been no change
// that would need a reload, not do the reload.
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
updateUI();
}
void updateUI() async {
await getUserIconData(widget.role, widget.id, widget.session).then((value){
// this ensures that a rebuild happens
setState(() => userIconData = value);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
// this only uses your circle avatar if the image is loaded, otherwise
// show a loading indicator.
child: userIconData != null ? CircleAvatar(
backgroundImage: Image.memory(userIconData!).image,
maxRadius: 20,
) : CircularProgressIndicator(),
),
),
);
}
}
Another way to do the same thing is to use a FutureBuilder.
class _FuncState extends State<Func> {
// using late isn't entirely safe, but we can trust
// flutter to always call didUpdateWidget before
// build so this will work.
late Future<Uint8List> userIconDataFuture;
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
userIconDataFuture =
getUserIconData(widget.role, widget.id, widget.session);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
child: FutureBuilder(
future: userIconDataFuture,
builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
if (snapshot.hasData) {
return CircleAvatar(
backgroundImage: Image.memory(snapshot.data!).image,
maxRadius: 20);
} else {
return CircularProgressIndicator();
}
},
),
),
),
);
}
}
Note that the loading indicator is just one option; I'd actually recommend having a hard-coded default for your avatar (i.e. a grey 'user' image) that gets switched out when the image is loaded.
Note that I've used null-safe code here as that will make this answer have better longevity, but to switch back to non-null-safe code you can just remove the extraneous ?, ! and late in the code.
The error message is pretty clear to me. userIconData is null when you pass it to the Image.memory constructor.
Either use FutureBuilder or a condition to check if userIconData is null before rendering image, and manually show a loading indicator if it is, or something along these lines. Also you'd need to actually set the state to trigger a re-render. I'd go with the former, though.

Stream builds a stack of Widget

So, I am using a stream to track the user's authentication state. Here is my setup, which works fine so far.
class Root extends ConsumerWidget {
final Widget _loadingView = Container(color: Colors.white, alignment: Alignment.center, child: UiHelper.circularProgress);
#override
Widget build(BuildContext context, ScopedReader watch) {
return watch(userStreamProvider).when(
loading: () => _loadingView,
error: (error, stackTrace) => _loadingView,
data: (user) => user?.emailVerified == true ? Products() : Login(),
);
}
}
The problem is, stream builds the UI multiple times. And I have a welcome dialog inside of my products page, which opens multiple times and as soon as I start the app it becomes a mess.
What should I do to avoid this scenario?
** Here I am using riverpod package
I personally recommend wrapping your widget with a StreamBuilder using the onAuthStateChanged stream. This stream automatically updates when the user change its state (logged in or out). Here is an example that may help you!
Stream<FirebaseUser> authStateChanges() {
FirebaseAuth _firebaseInstance = FirebaseAuth.instance;
return _firebaseInstance.onAuthStateChanged;
}
return StreamBuilder(
stream: authStateChanges(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// isLoggedIn
} else if (snapshot.hasData == false &&
snapshot.connectionState == ConnectionState.active) {
// isLoggedOut
} else {
// loadingView
}
},
);

How should I manage login page in flutter app

I am making an app which starts with a splash screen.
What I want is if the user signing for the first time it should redirect to overboarding screen.For that I have used shared preferences and stored a value to check if the user is new or not.And from overbearing it should go to login page after login it will check the user data exists or not. And in normal case after splash it should redirect to home screen.My code is working. But the problem is I don't know how to arrange it.
Main.dart -> (check logged in or not ) -> (if already logged in)Home.dart -> (else check first time login or not)-> (if first time login then)Onboarding Screen From there Login screen ->else Login Screen
If you need any more information just ask me
This is the code from main.dart
return SplashScreen.navigate(
name: 'assets/splash.flr',
next: (context) {
return AuthService().handleAuth();
},
startAnimation: 'Untitled',
until: () => Future.delayed(Duration(seconds: 4)),
backgroundColor: Colors.white,
);
This is AuthService().handleAuth() code
handleAuth() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.maybePop(context);
SharedPrefFunction().saveLoginPreference();
return CheckUser();
}
else{
return LoginScreen();}
});
}
This is the onboarding code
onTap: () {
Navigator.of(context)
.pushReplacementNamed(LoginScreen.loginRoute);
},
I want to go to AuthService().handleAuth() from onboarding.
How can I reach there from Onboarding screen or suggest me something better.
In your main.dart
bool loggedIn = false;
#override
void initState() {
super.initState();
isUserLoggedIn();
}
void isUserLoggedIn() async {
_loggedIn = await SharedPrefFunction().getLoginPreference()
setState(() => loggedIn = _loggedIn);
}
Widget build(BuildContext context) {
loggedIn ? LoginScreen() : SplashScreen()
}