what is the best way to show in an app if the user is online or offline?
Frontend -> Flutter
Backend -> Firestore Cloud and Firebase Auth.
I have a collection of users in Firestore that contains documents. Each document is a user and contain status field. In Flutter, I can update this field every time that user log in or log out but if you close the app it is not updated.
You can extend your statefulWidget State class with WidgetsBindingObserver
like
class _HomePageState extends State<HomePage>
with WidgetsBindingObserver
and initState method add WidgetsBinding.instance.addObserver(this);.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
Later overide didChangeAppLifecycleState method
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed)
//TODO: set status to online here in firestore
else
//TODO: set status to offline here in firestore
}
IT CAN DONE BY WidgetsBindingObserver();
class _HomeState extends State<Home> with WidgetsBindingObserver {...}
initialize it first
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
After add this function to listen for app state didChangeAppLifecycleState()
String? changes;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
final isBg = state == AppLifecycleState.paused;
final isClosed = state == AppLifecycleState.detached;
final isScreen = state == AppLifecycleState.resumed;
isBg || isScreen == true || isClosed == false
? setState(() {
// SET ONLINE
})
: setState(() {
//SET OFFLINE
});
print('CHANGES IS : $changes ');
}
String? changes Contains Your app State! Be Happy You Can what you want , This can notify Online status like Whatsapp Messenger!
MyCode :
isBg || isScreen == true || isClosed == false
? setState(() {
changes = "User is online"
})
: setState(() {
changes = "User is Offline"
});
print('CHANGES IS : $changes ');
You can catch in onerror.
You have to chenge souce as server
source: Source.server
users.get(GetOptions(source: Source.server))
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final CollectionReference users = _firestore.collection('users');
users.get(GetOptions(source: Source.server))
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
print(doc["first_name"]);
print(doc["last_name"]);
print(doc["gender"]);
print(doc["phone_number"]);
});
}).onError((error, stackTrace) {
print(error.toString());//Offline
Global.showSnackBar(context, error.toString(), false);
});
Related
I was originally following the code for the answer found here:
Check if an application is on its first run with Flutter
And I was incorporating it into the introduction_screen package on pub.dev
I successfully have it loading the page for my onboarding page on first load. Then when I am done with the onboarding page I try to set the shared preference value to 'true' so when I reload the app it will skip the onboarding page, but it does not work when i test in my emulator in VS Code.
I check the value on first book here:
class _MyAppState extends State<MyApp> {
bool isLoggedIn = false;
_MyAppState() {
MySharedPreferences.instance
.getBooleanValue("isfirstRun")
.then((value) => setState(() {
isLoggedIn = value;
}));
}
I load the onboarding screen if false here:
home: isLoggedIn ? MainPage() : OnBoard(),
My Shared Pref dart file is:
import 'package:shared_preferences/shared_preferences.dart';
class MySharedPreferences {
MySharedPreferences._privateConstructor();
static final MySharedPreferences instance =
MySharedPreferences._privateConstructor();
setBooleanValue(String key, bool value) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
myPrefs.setBool(key, value);
}
Future<bool> getBooleanValue(String key) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
return myPrefs.getBool(key) ?? false;
}
}
When the onboarding is complete I run this:
MySharedPreferences.instance.setBooleanValue("loggedin", true);
//replace with main page
Route route = MaterialPageRoute(builder: (context) => MainPage());
Navigator.pushReplacement(context, route);
If I hot reload in VS everything is ok, but if I restart with the the app it runs the onboarding screen each time.
You should check and change the isLoggedIn value in initstate function
For example
class _MyAppState extends State<MyApp> {
bool isLoggedIn = false;
#override
void initState() {
MySharedPreferences.instance
.getBooleanValue("isfirstRun")
.then((value) => setState(() {
isLoggedIn = value;
}));
super.initState();
}
#override
Widget build(BuildContext context) {
return Something...
}
Use async and await in a separate function and then use it in initState:
void verityFirstRun() async {
final verification = await SharedPreferences.getInstance();
isLoggedIn = verification.getBool("isfirstRun") ?? false;
}
#override
void initState() {
verityFirstRun();
super.initState();
}
Use this way of calling the SharedPreferences instance:
final verification = await SharedPreferences.getInstance();
This is what I'm trying to achieve using flutter GetX package but not working properly.
I have a Firestore document, if the document is changed I want to call an api and keep the data up to date as observable.
The code below seems to work but initial screen shows null error then it shows the data.
I don't know how I can make sure both fetchFirestoreUser() and fetchApiData() (async methods) returns data before I move to the home screen.
GetX StateMixin seems to help with async data load problem but then I don't know how I can refresh the api data when the firestore document is changed.
I'm not sure if any other state management would be best for my scenario but I find GetX easy compared to other state management package.
I would very much appreciate if someone would tell me how I can solve this problem, many thanks in advance.
Auth Controller.
class AuthController extends SuperController {
static AuthController instance = Get.find();
late Rx<User?> _user;
FirebaseAuth auth = FirebaseAuth.instance;
var _firestoreUser = FirestoreUser().obs;
var _apiData = ProfileUser().obs;
#override
void onReady() async {
super.onReady();
_user = Rx<User?>(auth.currentUser);
_user.bindStream(auth.userChanges());
//get firestore document
fetchFirestoreUser();
//fetch data from api
fetchApiData();
ever(_user, _initialScreen);
//Refresh api data if firestore document has changed.
_firestoreUser.listen((val) {
fetchApiData();
});
}
Rx<FirestoreUser?> get firestoreUser => _firestoreUser;
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => Login());
} else {
Get.offAll(() => Home());
}
}
ProfileUser get apiData => _apiData.value;
void fetchFirestoreUser() async {
Stream<FirestoreUser> firestoreUser =
FirestoreDB().getFirestoreUser(_user.value!.uid);
_firestoreUser.bindStream(firestoreUser);
}
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
#override
void onDetached() {}
#override
void onInactive() {}
#override
void onPaused() {}
#override
void onResumed() {
fetchApiData();
}
}
Home screen
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Obx(() =>
Text("username: " + AuthController.instance.apiData.username!))),
),
);
}
}
To be honest, I never used GetX so I'm not too familiar with that syntax.
But I can see from your code that you're setting some mutable state when you call this method:
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
Instead, a more robust solution would be to make everything reactive and immutable. You could do this by combining providers if you use Riverpod:
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
final authService = ref.watch(authRepositoryProvider);
return authService.authStateChanges();
});
final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
final userValue = ref.watch(authStateChangesProvider);
final user = userValue.value;
if (user != null) {
// note: this should also be turned into a provider, rather than using a static method
return RemoteService.getProfile(user.uid);
} else {
// decide if it makes sense to return null or throw and exception when the user is not signed in
return Future.value(null);
}
});
Then, you can just use a ConsumerWidget to watch the data:
#override
Widget build(BuildContext context, WidgetRef ref) {
// this will cause the widget to rebuild whenever the auth state changes
final apiData = ref.watch(apiDataProvider);
return apiData.when(
data: (data) => /* some widget */,
loading: () => /* some loading widget */,
error: (e, st) => /* some error widget */,
);
}
Note: Riverpod has a bit of a learning curve (worth it imho) so you'll have to learn it how to use it first, before you can understand how this code works.
Actually the reason behind this that you put your controller in the same page that you are calling so in the starting stage of your page Get.put() calls your controller and because you are fetching data from the API it takes a few seconds/milliseconds to get the data and for that time your Obx() renders the error. To prevent this you can apply some conditional logic to your code like below :
Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) :
I am using this plugin to update my location in firebase. When the app is in foreground everything works perfectly but as soon as my app goes in background then the location update service stops,I tried using didChangeAppLifecycleState but I can't seem to get it to work,
This is my implementaion so far..
class HomeTabPage extends StatefulWidget {
#override
_HomeTabPageState createState() => _HomeTabPageState();
}
class _HomeTabPageState extends State < HomeTabPage >
with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.detached) return;
final isBackground = state == AppLifecycleState.paused;
if (isBackground) {
print("### paused");
StreamSubscription < Position > backgroundStreamSubscription =
Geolocator.getPositionStream().listen((Position position) {
initPositionPoint = position;
Geofire.setLocation(
currentFirebaseUser.uid, position.latitude, position.longitude);
});
}
}
From Github issues #53 and #444 of the flutter-geolocator repo it seems that it doesn't support background location tracking. It seems that some folks have been using background_locator as an alternative, so you might want to look at that.
Recently I am developing first ever app in Flutter and I know the basics only.
So here's the problem which I am facing currently.
I have to Navigate One of the two screens, either Register or Home page according to old/new user, So what I did is here.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'Register.dart';
import 'HomePage.dart';
class Authenticate extends StatefulWidget {
#override
_AuthenticateState createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
String _userName = "";
#override
void initState() {
super.initState();
_getUserName();
}
_getUserName() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
_userName = pref.getString('userName') ?? "";
});
}
Widget build(BuildContext context) {
if (_userName == "") {
print('name : $_userName , NEW user'); // ERROR - why it's printing on console for old User ?
return Register();
} else {
return HomePage(_userName);
}
}
}
So the problem is, even if I am opening app through old user, it is printing the debug code written for new user and there's around 0.3 sec screen visibility of Register screen.
So what should I do to fix this?
The print is happening because this line of code:
SharedPreferences pref = await SharedPreferences.getInstance();
is asynchronous, which means that it might invoke in some later point in time. Because of that the build method is called first, then your _getUserName method finished, which causes a setState and invokes a build method again.
You might want to show some kind of loader screen until sharedPrefernces is initialized and then decide wheter to show Register or Home page.
Example code:
class _AuthenticateState extends State<Authenticate> {
String _userName = "";
bool _isLoading = true;
#override
void initState() {
super.initState();
_getUserName();
}
_getUserName() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
_userName = pref.getString('userName') ?? "";
_isLoading = false;
});
}
Widget build(BuildContext context) {
if (_isLoading) {
return Center(child: CupertinoActivityIndicator());
} else if (_userName == "") {
print('name : $_userName , NEW user'); // ERROR - why it's printing on console for old User ?
return Register();
} else {
return HomePage(_userName);
}
}
}
Try this:
class _AuthenticateState extends State<Authenticate> {
String _userName;
#override
void initState() {
super.initState();
_getUserName();
}
_getUserName() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
_userName = pref.getString('userName') ?? '';
});
}
Widget build(BuildContext context) {
if (_userName == null) {
return Center(child: CircularProgressIndicator());
} else if (_userName == '') {
print('name : $_userName , NEW user');
return Register();
} else {
return HomePage(_userName);
}
}
}
In this case, _userName is null initially and will only be assigned a value after the _getUserName() returns either an empty String or an old userName. When null, the widget will build a progress indicator instead. If you don't want that, just return a Container().
I'm developing an app that fetchs some data from the internet. In order to avoid issues with the internet connection I added the connectivity package.
If internet is connected when the app starts and then the internet connection is switched off, I can display a Container with the Text of "no internet". If I switch internet on again, the data is displayed.
The code to achive this is the following:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:connectivity/connectivity.dart';
class CheckNetworkPage extends StatefulWidget {
#override
_CheckNetworkPageState createState() => _CheckNetworkPageState();
}
class _CheckNetworkPageState extends State<CheckNetworkPage> {
StreamSubscription<ConnectivityResult> _networkSubscription;
Future<List<Data>> fetchData() async {
// Code to fetch data
}
#override
initState() {
super.initState();
fetchData();
_networkSubscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
_connectionStatus = result.toString();
print(_connectionStatus);
if (result == ConnectivityResult.wifi ||
result == ConnectivityResult.mobile ||
result == ConnectivityResult.none) {
print("Result: $result");
setState(() {});
}
});
}
// Cancel subscription after you are done
#override
dispose() {
super.dispose();
_networkSubscription.cancel();
}
#override
Widget build(BuildContext context) {
// More code. I can use the result of _connectionStatus to build my app
}
}
However, if the app starts without internet, when I switch it on, the data doesn't load, as it is fetched in initState().
How to fetch the data when it was no fetched before and internet connection is switched on?
You could store latest fetched data in a variable.
List<Data> fetchedData;
Future<List<Data>> fetchData() async {
// Code to fetch data
// Add this :
fetchedData = ...
}
Then in your listener, check whether this data is defined :
if (result == ConnectivityResult.wifi ||
result == ConnectivityResult.mobile ||
result == ConnectivityResult.none) {
print("Result: $result");
setState(() {});
// Add this :
if (fetchedData == null) fetchData()
}