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()
}
Related
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())) :
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 want to implement internet connectivity check into my app and I used official connectivity plugin and it is working great for displaying String Value but instead of showing string value in screen I want to display different widgets for connected and disconnected.
Here What I am Using
//
Widget result;
//
body: Container(
alignment: Alignment.center,
child: result != null ?
result : Text("unknown", style :
TextStyle(fontSize: 30,fontWeight: FontWeight.bold),
),
void checkStatus(){
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
if(
result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi){
Text("Connected", style:TextStyle(color:Colors.red));
} else {
Text("No InterNet", style:TextStyle(color:Colors.red));
}
});
}
#override
void initState() {
super.initState();
checkStatus();
}
And I am Getting 'unknown' value
try this
class Sample extends StatefulWidget {
#override
_SampleState createState() => _SampleState();
}
class _SampleState extends State<Sample> {
Widget result;
#override
void initState() {
super.initState();
checkStatus();
}
void checkStatus() async {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
result = Text("Connected to Mobile Network");
setState(() {});
} else if (connectivityResult == ConnectivityResult.wifi) {
result = Text("Connected to WiFi");
print("Connected to WiFi");
setState(() {});
} else {
result = Text("Unable to connect. Please Check Internet Connection");
setState(() {});
print("Unable to connect. Please Check Internet Connection");
}
}
#override
Widget build(BuildContext context) {
return Center(child: result);
}
}
Try this:
Use this package for checking Internet:
data_connection_checker:
And, Inside your stateful class create stream listener i.e and a boolean value.
StreamSubscription<DataConnectionStatus> listener; bool isConnected = true;
and Inside initState:
#override
void initState() {
super.initState();
listener = DataConnectionChecker().onStatusChange.listen((status) {
switch (status) {
case DataConnectionStatus.connected:
print('Data connection is available. $status');
setState(() {
isConnected = true;
});
break;
case DataConnectionStatus.disconnected:
print('You are disconnected from the internet. $status');
setState(() {
isConnected = false;
});
break;
}
});
}
Done, This will keep listening to changes in your internet status, Thus you can prompt user as you like. Cheers, Feel free to ask if confusion and if it helps upvote :D
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);
});
I am trying to use the following code to check an internet connection in flutter, we want to make our application usable offline but this code just seems to loop. Is there a better way to do this? Im calling this on a button press before calling an http api.
package is = connectivity: ^0.4.6+1
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile || connectivityResult == ConnectivityResult.wifi) {
//Connection Exists
} else {
//No connection
}
You should listen to network connection event instead of calling it directly.
Take a try with this:
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;
#override
initState() {
super.initState();
_networkSubscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
if (result == ConnectivityResult.mobile) {
// I am connected to a mobile network.
} else if (result == ConnectivityResult.wifi) {
// I am connected to a wifi network.
} else if (result == ConnectivityResult.none) {
// I am not connected any network.
}
});
}
#override
Widget build(BuildContext context) {
return Container();
}
// Be sure to cancel subscription after you are done
#override
dispose() {
super.dispose();
_networkSubscription.cancel();
}
}