Check Internet Connectivity after splash - flutter

I am a newbie to flutter. I am developing an app that contains a splash screen widget then the home screen will appear. I just want to check if there is any internet connection or not, if yes then it will go to the home screen otherwise it will close. I have checked the internet connection programmatically. It's ok, but without any internet connection, it goes to the home screen. Please help in this matter. Thanks in advance.
class Splashscreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Splashscreenmain();
}
class _Splashscreenmain extends State<Splashscreen>{
Helperfunction helperfunction = new Helperfunction();
#override
void initState() {
super.initState();
startSplashScreen();
}
startSplashScreen() async {
var duration = const Duration(seconds: 10);
if (helperfunction.internetConnection() != "No Internet"){
return Timer(duration, () {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (BuildContext context) {
return MyApp();
}));
});
}
else {
print(helperfunction.internetConnection());
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Container(
child: Image.asset('assets/images/splashscreen.png',fit: BoxFit.cover,
height: double.infinity,
width: double.infinity,
alignment: Alignment.center,),
),
);
}
}
I have printed internetConnection() function result but the 'else' part is not executed.

You want to close app if no internet connection.
You can do this with SystemNavigator.pop(). does not work for ios as in IOS an app should not exit itself.I would suggest you show the user a dialog to enable internet connectivty.
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
from docs .
Instructs the system navigator to remove this activity from the stack and return to the previous activity.
On iOS, calls to this method are ignored because Apple's human interface guidelines state that applications should not exit themselves.
This method should be preferred over calling dart:io's exit method, as the latter may cause the underlying platform to act as if the application had crashed.

Related

Flutter Ads Causing Delay

I recently ported my flutter app from admob_flutter to google_mobile_ads. I've successfully implemented banner ads according to the tutorial here. The ads appear as expected however they cause the rest of the app to wait for them to load, as if something is being awaited. Here is a visual of the problem:
no banner (expected behaviour)
with banner (buggy)
The delay is even worse when I want to load pages with more than a couple widgets. At first I thought this was because the app is being run on an emulator, however the phenomenon persists on physical phones with updated OSes as well. I found this issue and thought it might be related however using the suggested package didn't work, and again the problem occurs on updated versions of android as well ( > 10). Here is my Ad class:
class AnchoredAdaptiveBanner extends StatefulWidget {
#override
_AnchoredAdaptiveBannerState createState() => _AnchoredAdaptiveBannerState();
}
class _AnchoredAdaptiveBannerState extends State<AnchoredAdaptiveBanner> {
BannerAd _anchoredAdaptiveAd;
bool _isLoaded = false;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_loadAd();
}
Future<void> _loadAd() async {
// This was originally dynamic with an await, I changed it to static values for a sanity check, again same problem.
final AnchoredAdaptiveBannerAdSize size = AnchoredAdaptiveBannerAdSize(Orientation.portrait, width: 300, height: 50);
_anchoredAdaptiveAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: size,
request: AdRequest(),
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
setState(() {
_anchoredAdaptiveAd = ad as BannerAd;
_isLoaded = true;
});
},
onAdFailedToLoad: (Ad ad, LoadAdError error) {
ad.dispose();
},
),
);
return _anchoredAdaptiveAd.load();
}
#override
Widget build(BuildContext context) {
if (_anchoredAdaptiveAd != null && _isLoaded)
return Container(
width: _anchoredAdaptiveAd.size.width.toDouble(),
height: _anchoredAdaptiveAd.size.height.toDouble(),
child: AdWidget(ad: _anchoredAdaptiveAd),
);
else
return Container();
}
#override
void dispose() {
super.dispose();
_anchoredAdaptiveAd?.dispose();
}
}
My usage in my home widget for example:
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Container(
child: SafeArea(
child: Scaffold(
body: Builder(...),
floatingActionButton: Container(...),
bottomNavigationBar: AnchoredAdaptiveBanner()
),
),
);
}
}
As you can see nothing is being top-level awaited. I am currently on version 2.0.1 of google_mobile_ads.
Any idea what is causing this? Any help is appreciated!
The solution: Return SizedBox.shrink()
Why does this work?
Until the ad is loaded you're returning an empty container from your build method. If you don't pass height or width to the container, it expands as much as possible. In this case bottomNavigationBars can expand infinitely. This causes the rest of the widgets to be pushed out of screen and the page seems frozen until the ad loads. Using SizedBox.shrink() will have the container be 0x0 until it loads.

How to handle routes changes in flutter bloc depending on state

I have a question about how to handle the authentication state in flutter bloc.
Here is my login screen code.
class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
_getToken(context);
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return const HomeScreen();
}
if (state is UnAuthenticated) {
return const _SignInPage();
}
return Container(
color: const Color(0xFFF39360),
child: const Center(child: CircularProgressIndicator(color: Colors.white)),
);
},
);
}
As you can see I'm rendering Homescreen and SignInPage depending on the state.
I am also calling a _getToken(context); function during build of the screen. This functions executes a loading and I get an ugly CircularProgressIndicator while it is reading the storage. If i have the token i set the state as Authenticated.
Is there a better way to handle this so I don't get the progress indicator which is not spinning when I open the application?
Thanks in advance I hope you can help me!
There is nothing wrong with your bloc builder code. It will render a default progress indicator till bloc emit any new state. So if you want to show indicator while your function doing some work then you should emit a loading state and handle the same in the bloc builder and if you don't want to show anything at all for default case then you can use any other widget like an empty container or Sizedbox.shrink().
Hope it helps

Where to put Flutter AJAX?

I come from the web dev world. I have designed a Flutter app that needs to grab some JSON from the web very early on. I want my first screen to show up, and while it is being drawn, behind the scenes I want the JSON fetch to happen. There is a start button on Page 1 that will be disabled until the JSON is fetched. (But Page 1 will have some text info to keep the reader engaged until the fetch happens.)
Where would I stick the JSON fetch? Can I put it in initState of Page 1? Or can I initiate a call at the very start of main at the root of the app? Or somewhere else?
FWIW I use Provider for state management, if that helps?
Thanks a ton!
Calling it in the initState is best since your widgets still build. Since there will be a state change, make sure you use a stateful widget. Also, don't forget to call setState to re-enable the button. Example code:
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
void initState() {
super.initState();
jsonFetch();
}
void jsonFetch() {
// your functionality here
// on complete call:
/*
setState(() {
isJsonFetched = true;
});
*/
}
void doSomethingOnPressed(){
// Your functionality here
}
bool isJsonFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: ElevatedButton(
onPressed: (){
isJsonFetched
? doSomethingOnPressed()
:null;
},
child: Text('Press Me')
)
)
);
}
}

Navigator.pop from a FutureBuilder

I have a first screen which ask the user to enter to input, then when the users clicks on a button, the app goes on a second screen which uses a FutureBuilder to call an API.
If the API returns an error, I would like to go back to the previous screen with Navigator.pop. When I try to do that in the builder of the FutureBuilder, I get an error because I modify the tree while I am building it...
setState() or markNeedsBuild() called during build. This Overlay
widget cannot be marked as needing to build because the framework is
already in the process of building widgets
What is the proper way to go to the previous screen if an error occur?
class Stackoverflow extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<Flight>(
future: fetchData(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ScreenBody(snapshot.data);
} else if (snapshot.hasError) {
Navigator.pop(context, "an error");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
)
),
);
}
}
PS: I tried to use addPostFrameCallback and use the Navigator.pop inside, but for some unknown reason, it is called multiple times
You can not directly navigate when build method is running, so it better to show some error screen and give use chance to go back to last screen.
However if you want to do so then you can use following statement to do so.
Future.microtask(() => Navigator.pop(context));
I'd prefer to convert class into StateFullWidget and get rid of FutureBuilder
class Stackoverflow extends StatefulWidget {
#override
_StackoverflowState createState() => _StackoverflowState();
}
class _StackoverflowState extends State<Stackoverflow> {
Flight flight;
#override
void initState() {
super.initState();
fetchData().then((data) {
setState(() {
flight = data;
});
}).catchError((e) {
Navigator.pop(context, "an error");
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: flight != null ? ScreenBody(flight) : CircularProgressIndicator(),
),
);
}
}
and of cause pass context somewhere outside class is not good approach

How to get data from database after event from another screen

I came from Android Development, so, I'll try to explain my situation in Android terms in some cases. So, I have the MainScreen, which uses Scaffold and has FAB. The body of this screen is another widget (as Fragment in Android). When I click on the FAB (which is on MainScreen), bottom modal is opened. By this model, I can add data to the database. But I also want to add new data to my widget (fragment) at the same time. And I don't know in which method of lifecycle I should do call to DB. In android such method is onResume, but I didn't find it's analogue in Flutter.
I'll appreciate any help, thanks in advance!
UPD
There's my screen:
Bottom navigation and FAB is on the MainScreen. ListView is on the widget, which is body of MainScreen's Scaffold. Another my step is clicking on FAB. This click opens bottom modal
When I click save on the modal, data is saved to DB and modal close. And after this closing I want to see new database entry, which I just added from the modal. Now I can see new notes only after closing and then opening screen again. Here's my code:
class NotesScreen extends StatefulWidget {
#override
_NotesScreenState createState() => _NotesScreenState();
}
class _NotesScreenState extends State<NotesScreen> with WidgetsBindingObserver {
List<Note> _notes = new List<Note>();
#override
void initState() {
super.initState();
Fimber.i("Init");
WidgetsBinding.instance.addObserver(this);
NotesDataManager().getNotes().then((value) {
_notes = value;
setState(() {
});
});
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state){
case AppLifecycleState.resumed:
NotesDataManager().getNotes().then((value){
_notes = value;
setState(() {
});
});
Fimber.i("Resumed");
break;
case AppLifecycleState.inactive:
Fimber.i("Inactive");
break;
case AppLifecycleState.paused:
Fimber.i("Paused");
break;
case AppLifecycleState.suspending:
Fimber.i("Suspending");
break;
}
super.didChangeAppLifecycleState(state);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(AppColors.layoutBackgroundColor),
body: Container(
child: ListView.builder(
itemCount: _notes.length,
itemBuilder: (BuildContext context, int index) {
return NotesListItemWidget(note: _notes[index]);
},
),
),
);
}
}
As you can see, I added WidgetsBindingObserver, but it didn't help
You might want to use state management in your app. The easiest one is Provider. Your model should extend ChangeNotifier and whenever you add a new instance of model the app will be notified: notifyListener. In build you will implement ChangeNotifierProvider. Here is some tutorials:
Provider example 1
Provider example 2
Or you can just search for Provider