i have two screens A and B.In screen A iam calling a function periodically(i.e every 5 seconds).At the time of navigating to screen B i need to stop the function calling and when its back to screen A, the function call should be resumed.
Is there any way to do it?
Navigator doesn't expose the current route.
What you can do instead is use Navigator.popUntil(callback) as popUtil pass to the callback the current Route, which includes it's name and stuff.
final newRouteName = "/NewRoute";
bool isNewRouteSameAsCurrent = false;
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
}
return true;
});
if (!isNewRouteSameAsCurrent) {
Navigator.pushNamed(context, newRouteName);
}
Use This bool to check current screen and toggle your function .
From what i see is you can use the Timer class in the widget and manupulate based on your needs, I have created a sample example for you.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: PageOne()
);
}
}
class PageOne extends StatefulWidget {
const PageOne({Key? key}) : super(key: key);
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> {
Timer? timer;
#override
void initState() {
// TODO: implement initState
super.initState();
timer = Timer.periodic(const Duration(seconds: 2), (timer) {
printMethod("init");
});
}
printMethod(String type){
print("This is the $type print statement ");
}
#override
void dispose() {
super.dispose();
timer?.cancel();
print("First Page timer cancelled");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(onPressed: () async {
timer?.cancel();
var result = await Navigator.push(context, MaterialPageRoute(builder: (context)=> const PageTwo() ));
if(result)
{
timer = Timer.periodic(const Duration(seconds: 2), (timer) {
printMethod("init");
});
}
}, child: const Text("go to next page"),),
),
);
}
}
class PageTwo extends StatefulWidget {
const PageTwo({Key? key}) : super(key: key);
#override
_PageTwoState createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page two"),),
body: Center(
child: TextButton(onPressed: () async {
Navigator.of(context).pop(true);
}, child: const Text("go to prev page"),),
),
);
}
}
Let me know if it works
You can simply use bool to handle this case as follows :
class _ScreenAState extends State<ScreenA> {
bool runPeriodicFun = true;
void periodicFun() {
if (runPeriodicFun) {
//write your periodic logic
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: InkWell(
onTap: () {
setState(() {
runPeriodicFun = false;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => ScreenB()))
.then((value) {
setState(() {
runPeriodicFun = true;
});
periodicFun();
});
},
child: Container()),
);
}
}
when you go to Screen B then runPeriodicFun set to false and coming back to Screen A runPeriodicFun set to true. So it will use periodicFun block only when runPeriodicFun is true.
You will receive callback in then block in the Navigator.push method.
what i did is that, in timer function i checked the current page is at top of stack by
ModalRoute.of(context)!.isCurrent
what it did is that it will check the page by context which we pass if the page is at top it will continue the timer, if it navigates to another page then the above code will return false.
In false, i just stop the timer.
if(ModalRoute.of(context)!.isCurrent){
//timer running
}else{
_timer?.cancel();
}
so if the page is same timer will perform otherwise it will stop the timer.
Now for resuming the timer, under the build in screen where you called i just called the function from where timer get activated.
Widget build(BuildContext context) {
//call timer function
return SafeArea()}
i think this might solve the problem. if anyone identify any problem please comment it.
Related
I have a splash screen in my homepage activity which should then redirect to my second activity:
class _MyHomePageState extends State<MyHomePage> {
#override
void initState(){
super.initState();
Timer(const Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>
SecondScreen()
)
)
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child:FlutterLogo(size:MediaQuery.of(context).size.height)
);
}
}
class SecondScreen extends StatelessWidget { //checking if internet connection exists here
late StreamSubscription subscription;
var isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState(){
getConnectivity();
super.initState(); //initState() is undefined
}
getConnectivity() =>
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() => isAlertSet = true); //setState() is undefined
}
},
);
#override
void dispose() {
subscription.cancel();
super.dispose(); //dispose() is undefined
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment:MainAxisAlignment.center,
children:[
Image(
image: const AssetImage('images/logo.png'),
height: AppBar().preferredSize.height,),
const SizedBox(
width: 15,
),
Text(
widget.title
),
]
)
)
);
}
showDialogBox() => showCupertinoDialog<String>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('No internet connection'),
content: const Text('Please make sure you have an active internet connection to continue'),
actions: <Widget>[
TextButton(
onPressed: () async {
Navigator.pop(context, 'Cancel');
setState(() => isAlertSet = false);
isDeviceConnected =
await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() => isAlertSet = true);
}
},
child: const Text('OK'),
),
],
),
);
}
The flow is such that, in the homepage activity a splash screen will open and then it will redirect to the second activity which will check if the user has an active internet connection.
I tried changing the SecondScreen to statefulWidget, but I still keep getting the same error.
Stateless: A stateless widget is like a constant. It is immutable. If you want to change what is displayed by a stateless widget, you'll have to create a new one.
Stateful: Stateful widgets are the opposite. They are alive and can interact with the user. Stateful widgets have access to a method named setState, which basically says to the framework "Hello, I want to display something else. Can you redraw me please ?".
A stateless widget can only be drawn once when the Widget is loaded/built and cannot be redrawn based on any events or user actions.
This kind of widget has no state, so they can’t change according to an internal state, they only react to higher widget changes.
more information read this documentation StatefulWidget and StatelessWidget
convert in stateful widget
class SecondScreen extends StatefulWidget {
const SecondScreen({Key? key}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
there is no initState in a stateless widget but you can call a function after rebuild of a stateless widget using this:
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
// do something
print("Build Completed");
});
return Container(
color: Colors.blue,
child: WhatEverWidget()
);
}
}
There is a FirstScreen screen and there is a second SecondScreen called from the first screen, in the first screen a web socket is launched, data arrives on it, on the second screen, let's say there is a Text () widget into which you need to write data from the first screen when SecondScreen is already running. Which way to look?
You can manage this issue using state management like provider or bloc
var _channel;
var _screenShow = 1;
class FirstScreen extends StatefulWidget {
FirstScreen();
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
void _serverListen() {
_channel.stream.listen((message) {
String res = message.trim();
if (_screenShow == 1) {
// update widjet SecondScreen
} else if (_screenShow == 2) {
// update widjet ThirdScreen
} else if (_screenShow == 3) {
// update widjet FourthScreen
} else {
// update widjet FifthScreen
}
},
onDone: () {
print('ws channel closed');
},
onError: (error) {
print('ws error $error');
},
);
}
#override
void initState() {
super.initState();
_channel = IOWebSocketChannel.connect(Uri.parse('ws address'));
_serverListen();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('first screen'),
),
body: Text('FirstScreen'),
floatingActionButton: FloatingActionButton(
elevation: 5,
child: Icon(Icons.grid_on,),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
);
}
// SecondScreen
class SecondScreen extends StatefulWidget {
SecondScreen();
#override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: Text('update here from first screen'),
);
}
}
Use Getx Storage:
dependencies:
get_storage: ^2.0.3
Example:
In 1st Screen You Need to Store the value :
Initialize storage driver with await:
# main() async { await
GetStorage.init(); runApp(App()); }
use GetStorage through an instance or use directly GetStorage().read('key')
final box = GetStorage();
To read values you use read:
print(box.read('quote'));
// out: GetX is the best
In the 2nd Page you can remove the key:
To remove a key, you can use remove:
box.remove('quote');
If anyone is interested, implemented using this
https://pub.dev/packages/fbroadcast
this my code
Container(
child: Column(
children: jobProvider.jobs
.map(
(job) => JobTile(job),
)
.toList(),
),
)
how to load this data delay for 3 seconds ? here I display the listview data, before displaying it I want to display, the
return Container(
child: ProfileShimmer(),
);
Should be as easy as this:
import 'package:flutter/material.dart';
class PageWithDelayedView extends StatefulWidget {
const PageWithDelayedView({Key? key}) : super(key: key);
#override
_PageWithDelayedViewState createState() => _PageWithDelayedViewState();
}
class _PageWithDelayedViewState extends State<PageWithDelayedView> {
bool _initialized = false;
#override
void initState() {
super.initState();
// Schedule function call after the widget is ready to display
WidgetsBinding.instance?.addPostFrameCallback((_) {
_initialize();
});
}
void _initialize() {
Future<void>.delayed(const Duration(seconds: 3), () {
if (mounted) { // Check that the widget is still mounted
setState(() {
_initialized = true;
});
}
});
}
#override
Widget build(BuildContext context) {
if (!_initialized) {
return Text('Hold on a bit');
}
return Text('Yay, I\'m ready!');
}
}
From the docs of addPostFrameCallback, I see that
Post-frame callbacks cannot be unregistered. They are called exactly
once.
and so I wonder if addPostFrameCallback can be called after the widget is disposed since it cannot be unregistered?
I think the _postFrameCallback's callback will be called after the widget is disposed.
View the Flutter source code:
firstly, Post-frame callbacks cannot be unregistered. They are called exactly once. event registered after widget disposed, but still can't unregister.
secondly, when a new frame comes down, Flutter framework just check callback whether null, and then call the callback directly.
void handleBeginFrame(Duration? rawTimeStamp) {
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
}
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
assert(callback != null);
callback(timeStamp);
}
So, the callback will be called.
But, you can't update the UI (setState) in the callback because widgets have already been disposed.
example:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Listener(
onPointerDown: (event) {
setState(() {
isEnabled = true;
});
},
onPointerUp: (event) {
setState(() {
isEnabled = false;
});
},
child: Container(
height: 200,
width: 300,
color: Colors.red,
child: isEnabled ? Test1() : Text("null"),
),
),
),
);
}
}
class Test1 extends StatefulWidget {
#override
_Test1State createState() => _Test1State();
}
class _Test1State extends State<Test1> {
#override
Widget build(BuildContext context) {
return Text("hahah");
}
_onFrameStart(Duration duration) {
print("in _onFrameStart");
}
#override
void dispose() {
SchedulerBinding.instance.addPostFrameCallback(_onFrameStart);
super.dispose();
}
}
In other words, instead of fixed 15 seconds, is there a way to, to tell it to stop showing when my async function is finished?
If you are using a separate screen for Splash screen, you can simply await the async function you have and the use Navigator.pushReplacement() to open your Main Screen
Example:
class SplashScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return SplashScreenState();
}
}
class SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
handleSplashscreen();
}
void handleSplashscreen() async {
// Wait for async to complete
await someAsyncFunction();
// Open Main page
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomePage(user: null)));
}
Future<void> someAsyncFunction() async {
// Do some Network or other stuff
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(child: Text("Loading...")),
);
}
}
If you are simply showing a loader in the same screen while async operation is being done, you can use a FutureBuilder as others suggested.
Or you can conditionally show loader and Main UI using a boolean and setState().
Example:
class MainPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MainPageState();
}
}
class MainPageState extends State<MainPage> {
// Boolean to show/ hide loader
bool isLoading = true;
#override
void initState() {
super.initState();
handleAsync();
}
void handleAsync() async {
// Wait for async to complete
await someAsyncFunction();
// Open Main page
setState(() {
isLoading = false;
});
}
Future<void> someAsyncFunction() async {
// Do some Network or other stuff
}
#override
Widget build(BuildContext context) {
// Using isLoading to check whether async function is complete
return isLoading
? Container(
child: Center(child: Text("Loading...")),
)
: Container(
child: Text("The Actual screen"),
);
}
}
Hope it helps.
Use FutureBuilder from here in the page and show splash screen image when the data is not loaded.