Add check internet connection bloc to another screen flutter - 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),

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.

Futurebuilder is not updating data from firestore

So i am having issue with futurebuilder i want my app to update when a bool is set true but it wasn't working at all so i added a line to to see if the value of bool is changing or not and released it's not changing.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:databse_web_test/database_services/getsocials.dart';
import 'package:flutter/material.dart';
import 'database_services/request.dart';
class RequestWidget extends StatefulWidget {
RequestWidget({Key? key}) : super(key: key);
#override
State<RequestWidget> createState() => _RequestWidgetState();
}
class _RequestWidgetState extends State<RequestWidget> {
String Doc = "EobkN9fONF4IxmpErB1n";
CollectionReference request = FirebaseFirestore.instance
.collection('socails')
.doc("daaJgE8Pz5UQIlNh47UsmwWcqNi1")
.collection("requests");
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: request.doc("EobkN9fONF4IxmpErB1n").get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return const Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
bool isRequested = data["isRequested"];
bool isApproved = data["isApproved"];
if (data["isRequested"] == true && data['isApproved'] == true) {
return GetSocialData();
}
// if (data['isApproved'] == false && data['isRequested'] == true) {
// return Column(
// children: [
// data['isApproved'] == false
// ? const CircularProgressIndicator()
// : GetSocialData()
// ],
// );
// }
if (data['isApproved'] == false && data["isRequested"] == false) {
return Center(
child: ElevatedButton(
onPressed: () {
SendRequest().updateUserData(
isApproved: false, isRequested: true);
setState(() {});
},
child: const Text("data send")));
} else {
return Column(children: [
CircularProgressIndicator(),
Text(snapshot.data!.data().toString())
]);
}
} else {
return const Text("Loading database");
}
});
// if (isRequested == true && isApproved == false) {
// return Center(
// child: ElevatedButton(
// onPressed: () {
// SendRequest()
// .updateUserData(isApproved: false, isRequested: true);
// },
// child: const Text("data send")));
// } else {
// return GetSocialData();
// }
}
}
i really don't know whats wrong since im new to flutter i dont know that much. if i were to use text widget to know if the value is changing i get to know that value isn't changing. this web app is connect to another android app and value of that bool is gonna be updated by that app
A flutter builder it is a one time read, because, if you want to use a realtime read, use a streambuilder, check that in documentation : Flutter Cloud Firestore.
FutureBuilder is used for one time response, like taking an image from Camera, getting data once from native platform (like fetching device battery), getting file reference, making an http request etc.
On the other hand, StreamBuilder is used for fetching some data more than once, like listening for location update, playing a music, stopwatch, etc.
In your case you should use StreamBuilder

authStateChanges only work if in debug mode

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.

How to wait multiple bloc events in same bloc

I have one bloc with multiple events. Here I load categories and locations and wait using BlocListener. But my condition for show circular progress indicator work incorrectly and after load categories and locations also shows. How I can use bloc correctly in this case?
Code
apiDataBloc.add(LoadCategoriesEvent());
apiDataBloc.add(LoadLocationsEvent());
------------------------
return BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, state) {
if (state is CategoriesLoaded) {
categories = state.categories;
print("Categories loaded");
print(categories.length);
}
},
child: BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, s) {
if (s is LocationsLoaded) {
locations = s.locations;
print("Locations loaded");
print(locations.length);
}
},
child: locations != null &&
categories != null &&
categories.length > 0 &&
locations.length > 0
? Container(child: Center(child: Text('Categories and locations loaded!')))
: Container(child: Center(child: CircularProgressIndicator())),
),
);
I tried also like this but doesn't work.
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is LocationsLoaded) {
print("Locations loaded");
locations = state.locations;
print(locations.length);
return BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, s) {
if (s is CategoriesLoaded) {
print("Categories loaded");
categories = s.categories;
print(categories.length);
return Container(
child: Center(
child: Text('Categories and locations loaded!')));
}
return Container(
child: Center(child: CircularProgressIndicator()));
},
);
}
return Container(child: Center(child: CircularProgressIndicator()));
},
),
);
You should create one state DataLoaded with 2 fields categories and locations
Something like that:
class DataLoaded extends ApiDataState {
const DataLoaded(
this.categories,
this.locations,
);
final List<Type> categories;
final List<Type> locations;
#override
String toString() => 'DataLoaded';
}
Then you need to fetch data from API in the ApiDataBloc class:
class ApiDataBloc extends Bloc<YourEventType, ApiDataState> {
ApiDataBloc() : super(YourInitialState());
#override
Stream<ApiDataState> mapEventToState(YourEventType event) async* {
if (event is YourFetchApiEvent) {
yield YourLoadingState();
final categories = await _fetchCategories();
final locations = await _fetchLocations();
yield DataLoaded(categories,locations);
}
}
}
and the final step is BlocBuilder in your widget:
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is YouLoadingState) {
return Center(child: CircularProgressIndicator());
}
if (state is DataLoaded) {
print(state.locations);
print(state.categories);
return Center(
child: Text('Categories and locations loaded!'),
);
}
},
),
);
I would place the logic into the bloc. If I understand correctly, you get an event triggered as soon as the data is loaded. Then you could create 2 variables in the bloc bool categoriesLoaded, locationsLoaded which you set true upon the event. In mapEventToState you could forward from each of those event mappers to a common event mapper that checks if both variables are true and sends the proper state then. An inProgress state could display which of the data streams has already been loaded.
I know what you meant.
Example Case:
#some_bloc.dart (not in event or state file)
on<someEventNo1>((......) =>
emit(LoadingState());
emit(EmitResultAPI());
on<someEventNo2>((......) =>
emit(LoadingState());
emit(someState());
#main.dart
someMethod() {
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
BlocProvider.of<SomeBloc>(context).add(someEventNo2());
}
If you do your code like that, bloc builder will not catch state change when someEventNo1 emits EmitResultAPI, because you are sending 2 consecutive BlocProvider.of<>().
Solution:
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
Future.delayed(Duration(miliseconds: 100)).then((valueFuture) => BlocProvider.of<SomeBloc>(context).add(someEventNo2()));

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
}
},
);