how to await for network connectivity status in flutter - flutter

I have used connectivity_plus and internet_connection_checker packages to check the internet connectivity.
The problem occured is , the app works perfectly fine as expected when the app start's with internet on state. But when the app is opened with internet off, the dialog isn't shown !!
I assume this is happening because the build method is called before the stream of internet is listened.
Code :
class _HomePageState extends State<HomePage> {
late StreamSubscription subscription;
bool isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState() {
getConnectivity();
super.initState();
}
getConnectivity() {
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() {
isAlertSet = true;
});
}
},
);
}
#override
void dispose() {
subscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
);
}
showDialogBox() => showDialog(/* no internet dialog */)
Extending the question: Is it assured that this works for all the pages ?
if yes, how ?
if not , how to overcome this?

First of all you need to listen for internet connectivity in your app first screen which is probably app.dart
GlobalKey<NavigatorState> navigatorKey = GlobalKey();
final noInternet = NoInternetDialog();
class TestApp extends StatefulWidget {
#override
State<TestApp> createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
void initState() {
super.initState();
checkInternetConnectivity();
}
#override
Widget build(BuildContext context) {
return MaterialApp(...);
}
Future<void> checkInternetConnectivity() async {
Connectivity().onConnectivityChanged.getInternetStatus().listen((event)
{
if (event == InternetConnectionStatus.disconnected) {
if (!noInternet.isShowing) {
noInternet.showNoInternet();
}
}
});
}
}
Make the screen stateful in which you are calling MaterialApp and in initState of that class check for your internet connection, like above
You are saying how can I show dialog when internet connection changes for that you have to create a Generic class or extension which you can on connectivity change. You have to pass context to that dialogue using NavigatorKey
class NoInternetDialog {
bool _isShowing = false;
NoInternetDialog();
void dismiss() {
navigatorKey.currentState?.pop();
}
bool get isShowing => _isShowing;
set setIsShowing(bool value) {
_isShowing = value;
}
Future showNoInternet() {
return showDialog(
context: navigatorKey.currentState!.overlay!.context,
barrierDismissible: true,
barrierColor: Colors.white.withOpacity(0),
builder: (ctx) {
setIsShowing = true;
return AlertDialog(
elevation: 0,
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(3.0.h),
content: Container(...),
);
},
);
}
}

Use checkConnectivity to check current status. Only changes are exposed to the stream.
final connectivityResult = await Connectivity().checkConnectivity();

Related

Navigate from notification via beamer

I want to navigate to a specific page via beamer from a notification click.
In my main.dart I initialze my app and fcm. The class 'PushNotificationReceiver' should handle the notification logic.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PushNotificationReceiver.instance.initialize();
runApp(MultiProvider(providers: [
// Some of my providers
], builder: (context, _) => MyApp()));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
PushNotificationReceiver.instance.registerNotifications((route) => {
context.beamToNamed(route)
});
}
#override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {
return MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(delegate: _beamerDelegate),
);
}
}
}
I implemented the functions to receive and show local notifications but to simplify it I only paste the code for the click (removed null checks as well).
class PushNotificationReceiver {
static PushNotificationReceiver _instance;
void Function(String route) navigateFunction;
static PushNotificationReceiver get instance {
if (_instance == null) {
_instance = new PushNotificationReceiver();
}
return _instance;
}
Future<void> initialize() async {
await Firebase.initializeApp();
}
void registerNotifications(void Function(String route) navigateFunction) {
this.navigateFunction = navigateFunction;
// Called the other functions to receive notifications, but excluded them for simplicity.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
this.navigateFunction("/MyPage/${message.data["id"]}");
});
}
}
When I click on the notification I get the following error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: 'package:beamer/src/beamer.dart': Failed assertion: line 40 pos 14: 'BeamerProvider.of(context) != null': There was no Router nor BeamerProvider in current context. If using MaterialApp.builder, wrap the MaterialApp.router in BeamerProvider to which you pass the same routerDelegate as to MaterialApp.router.
I tried it first without a function that I pass in and a GlobalKey in the main.dart with the same result.
Any suggestions?
Found the solution.
My first approach of a global key works if I wrap my MaterialApp.router in a Beamerprovider (like the error message suggested).
final GlobalKey myGlobalKey = GlobalKey();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PushNotificationReceiver.instance.initialize();
runApp(MultiProvider(providers: [
// Some of my providers
], builder: (context, _) => MyApp()));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
PushNotificationReceiver.instance.registerNotifications();
}
#override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {
return BeamerProvider(
key: myGlobalKey,
routerDelegate: _beamerDelegate,
child: MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(
delegate: _beamerDelegate
)
)
);
}
}
}
That leads to my push notification receiver:
class PushNotificationReceiver {
static PushNotificationReceiver _instance;
static PushNotificationReceiver get instance {
if (_instance == null) {
_instance = new PushNotificationReceiver();
}
return _instance;
}
Future<void> initialize() async {
await Firebase.initializeApp();
}
void registerNotifications(void Function() {
// Called the other functions to receive notifications, but excluded them for simplicity.
FirebaseMessaging.onMessageOpenedApp.listen((message) {
myGlobalKey.currentContext.beamToNamed("/MyPage/${message.data["id"]}");
});
}
}
I hope this will help some others too.

How can i show some loading screen or splash screen while flutter application loads up

I have been working on an app recently. I want to check if the user is logged in and is verified when my app loads up. So I created a Wrapper class to check if the user is logged in and is verified. Then accordingly I would show them either login screen or home screen.
I have assigned home : Wrapper(), in Main.dart .
After that I have wrapper class as
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
// checking if there is user and the user is verified
bool _isAuth() {
if (user != null && user.isVerified) {
return true;
}
return false;
}
return _isAuth() ? MainScreen() : Authenticate();
}
}
This works fine but the problem is it first flashes the login page and then takes me to the homepage if the user is logged in and is verified but it just works fine if the user is not logged in see gif image here
It probably shows the login page because of the way your logic is being handled. you should do this in initState instead of the build method. There are two ways to do this you can either use your wrapper as redirection class or use the build method like you're already doing to toggle the view.
First Method (uses redirection)
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
super.initState();
final user = Provider.of<User>(context, listen: false);
var _isAuth = user != null && user.isVerified;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _isAuth ? MainScreen() : Authenticate()),
);
}
#override
Widget build(BuildContext context) {
return CircularProgressIndicator();
}
}
Second Method (uses build method):
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
bool _isAuth = false;
bool _isLoading = true;
#override
void initState() {
super.initState();
final user = Provider.of<User>(context, listen: false);
setState(() {
_isAuth = user != null && user.isVerified;
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return _isLoading
? CircularProgressIndicator()
: _isAuth
? MainScreen()
: Authenticate();
}
}

How to show message to the snackBar when user does not have internet in flutter app?

if internet connection is off i want to show "No internet connection" message in SnackBar.
if internet connection is on it will show UI.
This is my code:
import 'dart:io';
import 'package:flutter/material.dart';
import 'main.dart';
class InternetCheck extends StatefulWidget {
#override
_InternetState createState() => _InternetState();
}
class _InternetState extends State<InternetCheck> {
bool _isInternet = true;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
checkInternet() async {
try {
final response = await InternetAddress.lookup('example.com'); // google
if (response.isNotEmpty && response[0].rawAddress.isNotEmpty) {
_isInternet = true; // internet
setState(() {});
}
} on SocketException catch (_) {
_isInternet = false; // no internet
setState(() {
_showSnackBar();
});
}
return _isInternet;
}
#override
void initState() {
checkInternet();
super.initState();
}
void _showSnackBar() {
SnackBar snackBar = SnackBar(content: Text('No Connection'));
_scaffoldKey.currentState.showSnackBar(snackBar);
}
#override
Widget build(BuildContext context) {
// checkInternet();
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
body: _isInternet == true ? Text('internet') : Text('no Internet'),
),
);
}
}
using my code when app run first time with internet off it show SnackBar but when i switch internet on and again switch internet off then it will not showing snakbar.
Check out the connectivity package. It allows you to listen for network state changes :
#override
initState() {
super.initState();
subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
if(connectivityResult == ConnectivityResult.none)
_showSnackbar();
})
}
// Be sure to cancel subscription after you are done
#override
dispose() {
super.dispose();
subscription.cancel();
}

Accessing Flutter context when creating StatefulWidget

I'm having trouble accessing a services object when initializing a stateful widget. The problem comes from the context object not being available in initState.
I'm using InheritedWidget to inject a services object in my main.dart file like so
void main() async {
final sqflite.Database database = await _openDatabase('db.sqlite3');
runApp(
Services(
database: database,
child: MyApp(),
),
);
}
The Services object is quite straightforward. It will have more than just the database as a member. The idea is that the widgets don't need to know if a local database, local cache, or remote server is being accessed.
class Services extends InheritedWidget {
final Database database;
const Services({
Key key,
#required Widget child,
#required this.database,
}) : assert(child != null),
assert(database != null),
super(key: key, child: child);
Future<List<models.Animal>> readAnimals() async {
return db.readAnimals(database: this.database);
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
static Services of(BuildContext context) {
return context.inheritFromWidgetOfExactType(Services) as Services;
}
}
The trouble comes in my _HomePageState state when I want to load all the animals from the database. I need to access the Services object. I cannot access the Services object in initState so I am using didChangeDependencies. A problem comes when the home page is removed from the stack. It seems didChangeDependences is called and the access to the context object is illegal. So I created an _initialized flag that I can use in didChangeDependencies to ensure I only load the animals the first time. This seems very inelegant. Is there a better way?
class _HomePageState extends State<HomePage> {
bool _initialized = false;
bool _loading = false;
List<Animal> _animals;
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.of(this.context).appName),
),
body: _HomeBody(
loading: this._loading,
animals: this._animals,
),
);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (!this._initialized) {
this._initialized = true;
this._loadAnimals();
}
}
void _loadAnimals() async {
this.setState(() {
this._loading = true;
this._animals = null;
});
final List<Animal> animals = await Services.of(this.context).readAnimals();
this.setState(() {
this._loading = false;
this._animals = animals;
});
}
}
For that case you could use addPostFrameCallback of your WidgetsBinding instance to execute some code after your widget was built.
_onLayoutDone(_) {
this._loadAnimals();
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}

Where I can get information to make the WidgetsBindingObserver works again?

After upgrading to Flutter 0.7.3 channel beta (Dart 2.1.0-dev.1.0.flutter-ccb16f7282) the WidgetsBindingObserver is not working.
It worked before and after login if AppLifecycleState paused, inactive or suspended it returns to main page. But seems it doesnt work with new update. My question is where I can get information to see how to make the WidgetsBindingObserver works again.
// Statefull HomePage
class PersonalLoginPage extends StatefulWidget {
const PersonalLoginPage({ Key key }) : super(key: key);
#override
_PersonalLoginPageState createState() => new _PersonalLoginPageState();
}
class _PersonalLoginPageState extends State<PersonalLoginPage> with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
bool _appStatePause;
// TODO: initState function
#override
void initState() {
print("initState Starting Now .......................");
super.initState();
authenticateUser();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
Timer _timer;
setState(() {
_appLifecycleState = state;
if (_appLifecycleState == AppLifecycleState.paused ||
_appLifecycleState == AppLifecycleState.inactive ||
_appLifecycleState == AppLifecycleState.suspending) {
_appStatePause = true;
print("New Timer Starting Now .......................");
_timer = Timer.periodic(Duration(seconds: 60), _callback);
} else {
_appStatePause = false;
}
});
}
void _callback(_timer) {
if (_appStatePause == true) {
print("Timer Finished without cancel...................");
setState(() {
Navigator.push(
context,
SlideRightRoute(widget: MyApp()),
);
});
} else {
_timer.cancel();
print("Timer cancel now................................");
}
}
// TODO: authenticateUser function
Future authenticateUser() async {
……
……
……
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// TODO: main build Widget
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: new Text(“Hello World”)
)
);
}