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()
);
}
}
Related
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.
I am working in Riverpod Auth flow boilerplate application.
I want to use common loading screen for all async function even login and logout. Currently I have AppState provider if Appstate loading i show loading screen. it's working fine for login but i wonder it’s good way or bad way.
Can i use this loading screen for all async task in the App?
AuthWidget:
class AuthWidget extends ConsumerWidget {
const AuthWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
AppState appState = ref.watch(appStateProvider);
if(appState.isLoading){
return const Center(child: CircularProgressIndicator(color: Colors.red),);
}
return appState.isAuthenticated ? const HomePage() : const SignIn();
}
}
AppState:
class AppState {
User? user;
bool isLoading;
bool isAuthenticated;
AppState(this.user, this.isLoading, this.isAuthenticated);
}
AuthRepository:
class AuthRepository extends StateNotifier<AppState>{
AuthRepository() : super(AppState(null,false,false));
Future<void> signIn()async {
state = AppState(null,true,false);
await Future.delayed(const Duration(seconds: 3));
User user = User(userName: 'FakeUser', email: 'user#gmail.com');
AppState appState = AppState(user, false, true);
state = appState;
}
}
final appStateProvider = StateNotifierProvider<AuthRepository,AppState>((ref){
return AuthRepository();
});
To answer your question : Yes you can.
The only thing I'd change here is the content of your AppState : I'd use a LoadingState dedicated to trigger your Loader instead.
Here is how I like to manage screens with a common loader in my apps.
1 - Create a LoadingState and provide it
final loadingStateProvider = ChangeNotifierProvider((ref) => LoadingState());
class LoadingState extends ChangeNotifier {
bool isLoading = false;
void startLoader() {
if (!isLoading) {
isLoading = true;
notifyListeners();
}
}
void stopLoader() {
if (isLoading) {
isLoading = false;
notifyListeners();
}
}
}
2 - Define a base page with the "common" loader
class LoadingContainer extends ConsumerWidget {
const LoadingContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(loadingStateProvider);
return Stack(
children: [
child,
if (state.isLoading)
const Center(child: CircularProgressIndicator())
else
const SizedBox(),
],
);
}
}
3 - Implement this widget whenever I need to handle loading datas.
return Scaffold(
backgroundColor: AppColor.blue,
body: LoadingContainer(
child: ...
And then I simply have to update my loadingStateProvider and it's isLoading value from a Controller or the Widget directly
If you want a centralized/common async calls, the InheritedWidget is ideal for that, you can just add a method and call it from anywhere down stream and because the call is offloaded with async, you can attach extra arguments and add usefull functionality such as a live update instead of relying on stuff like .then(). This example might not be as simple as FDuhen's but you can mix them together if you want to not use keys
AppState now is a widget and contains trigers that rely on global keys to rebuild the correct components, here i assumed that you actualy want to have an common overlay and not a loading screen widget, if not using a Navigator would be batter
Using keys is specially good if you end up implementing something this line, <token> been just a number that references a group of widgets
key: AppState.of(ctx).rebuild_on_triger(<token>)
class App_State_Data {
GlobalKey? page_key;
bool is_logged = false;
bool loading_overlay = false;
String loading_message = '';
}
class AppState extends InheritedWidget {
final App_State_Data _state;
bool get is_logged => _state.is_logged;
bool get should_overlay => _state.loading_overlay;
String get loading_message => _state.loading_message;
void page_rebuild() {
(_state.page_key!.currentState as _Page_Base).rebuild();
}
GlobalKey get page_key {
if (_state.page_key == null) {
_state.page_key = GlobalKey();
}
return _state.page_key!;
}
void place_overlay(String msg) {
_state.loading_message = msg;
_state.loading_overlay = true;
page_rebuild();
}
void clear_overlay() {
_state.loading_message = '';
_state.loading_overlay = false;
page_rebuild();
}
Future<void> triger_login(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 2));
_state.is_logged = true;
clear_overlay();
}
Future<void> triger_logout(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 1));
_state.is_logged = false;
clear_overlay();
}
AppState({Key? key, required Widget child})
: this._state = App_State_Data(),
super(key: key, child: child);
static AppState of(BuildContext ctx) {
final AppState? ret = ctx.dependOnInheritedWidgetOfExactType<AppState>();
assert(ret != null, 'No AppState found!');
return ret!;
}
#override
bool updateShouldNotify(AppState old) => true;
}
Here i added it as the topmost element making it like a global data class with is not necessary, you can split the state content and add just the necessary to where its needed
void main() => runApp(AppState(child: App()));
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return MaterialApp(
home: Scaffold(
body: Page_Base(
key: AppState.of(ctx).page_key,
),
),
);
}
}
class Page_Base extends StatefulWidget {
final GlobalKey key;
const Page_Base({
required this.key,
}) : super(key: key);
#override
_Page_Base createState() => _Page_Base();
}
class _Page_Base extends State<Page_Base> {
Widget build_overlay(BuildContext ctx) {
return Center(
child: Container(
width: double.infinity,
height: double.infinity,
color: Color(0xC09E9E9E),
child: Center(
child: Text(AppState.of(ctx).loading_message),
),
),
);
}
#override
Widget build(BuildContext ctx) {
return Stack(
children: [
AppState.of(ctx).is_logged ? Page_Home() : Page_Login(),
AppState.of(ctx).should_overlay ? build_overlay(ctx) : Material(),
],
);
}
void rebuild() {
// setState() is protected and can not be called
// from outside of the this. scope
setState(() => null);
}
}
Using AppState is the best part, just because the widget does not have to call more than 1 function and it will rebuild with the correct data on complition
class Page_Login extends StatelessWidget {
const Page_Login({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_login('Login'),
child: Container(
width: 200,
height: 200,
color: Colors.greenAccent,
child: Text('Page_Login'),
),
),
);
}
}
class Page_Home extends StatelessWidget {
const Page_Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_logout('Logout'),
child: Container(
width: 200,
height: 200,
color: Colors.blueAccent,
child: Text('Page_Home'),
),
),
);
}
}
Global loading indicator
If you want a centralized loading indicator to use in your whole app you could take advantage of Overlay's, which flutter already uses for dialogs, popups, bottom sheets etc. This way we don't introduce new widget in the widget tree.
If you only want to toggle between loading states you can use a StateProvider to handle the simple boolean value, else you could create a State/Change Notifier. This way you decouple your loading state from your AppState
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(const ProviderScope(child: MaterialApp(home: GlobalLoadingIndicator(child: Home()))));
// This widget should wrap your entire app, but be below MaterialApp in order to have access to the Overlay
class GlobalLoadingIndicator extends ConsumerStatefulWidget {
final Widget child;
const GlobalLoadingIndicator({required this.child, Key? key}) : super(key: key);
#override
ConsumerState createState() => _GlobalLoadingIndicatorState();
}
class _GlobalLoadingIndicatorState extends ConsumerState<GlobalLoadingIndicator> {
//We need to cache the overlay entries we are showing as part of the indicator in order to remove them when the indicator is hidden.
final List<OverlayEntry> _entries = [];
#override
Widget build(BuildContext context) {
ref.listen<bool>(loadingProvider, (previous, next) {
// We just want to make changes if the states are different
if (previous == next) return;
if (next) {
// Add a modal barrier so the user cannot interact with the app while the loading indicator is visible
_entries.add(OverlayEntry(builder: (_) => ModalBarrier(color: Colors.black12.withOpacity(.5))));
_entries.add(OverlayEntry(
builder: (_) =>const Center(
child: Card(child: Padding(padding: EdgeInsets.all(16.0), child: CircularProgressIndicator())))));
// Insert the overlay entries into the overlay to actually show the loading indicator
Overlay.of(context)?.insertAll(_entries);
} else {
// Remove the overlay entries from the overlay to hide the loading indicator
_entries.forEach((e) => e.remove());
// Remove the cached overlay entries from the widget state
_entries.clear();
}
});
return widget.child;
}
}
We insert the GlobalLoadingIndicator high up in the widget tree although anywhere below the MaterialApp is fine (as long as it can access the Overlay via context).
The GlobalLoadingIndicator wont create extra widgets in the widget tree, and will only manage the overlays, here I add two overlays, one is a ModalBarrier which the user from interacting with widgets behind itself. And the other the actual LoadingIndicator. You are free to not add the ModalBarrier, or make it dismissible (or even if you decide to create a more complex loadingProvider, customize it in case you need to cater different use cases).
A sample usage after you have this set up is just switching the state of the loadingProvider, most of the times you would do this programatically, but for interactiveness I'll use a Switch :
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
Future.delayed(const Duration(seconds: 4)).then((value) {
ref.read(loadingProvider.notifier).state = false;
});
},
title: const FlutterLogo(),
),
));
}
}
You can fiddle with this snippet in dartpad
Result:
Per Screen/Section loading indicator
As a side note when displaying loading states inside components of the app I recommend you to use an AnimatedSwitcher , as it fades between the widgets , super handy when dealing with screens which can change content abruptly.
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(ProviderScope(child: MaterialApp(home: Home())));
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
},
title: AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: isLoading?CircularProgressIndicator():FlutterLogo()
),
),
));
}
}
I am making a list of stateless widget as shown below and passing the id as the parameter to the widgets.
Code for cartPage:-
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
bool loading=true;
List<CartTile> cartTiles=[];
#override
void initState() {
super.initState();
if(currentUser!=null)
getData();
}
getData()async
{
QuerySnapshot snapshot=await cartReference.doc(currentUser.id).collection('cartItems').limit(5).get();
snapshot.docs.forEach((doc) {
cartTiles.add(CartTile(id: doc.data()['id'],index: cartTiles.length,));
});
setState(() {
loading=false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: loading?Center(child:CircularProgressIndicator():SingleChildScrollView(
child: Column(
children: cartTiles,
),
),
);
}
}
Code for CartTile:-
class CartTile extends StatelessWidget {
final String id;
CartTile({this.id,});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: productReference.doc(id).snapshots(),
builder: (context,snapshot)
{
//here am using the snapshot to build the cartTile.
},
);
}
}
So, my question is whenever I will call setState in my homepage then will the stateless widget be rebuilt and increase my document reads. Because i read somewhere that when we pass the same arguments or parameters to a stateless widget then due to its cache mechanism it doesn't re build. If it will increase my reads then is there any other way to solve this problem?
FLutter:
How to display video in video_player from the location of path_provider ?
you can copy paste run full code below
In demo, I use getApplicationDocumentsDirectory. you can print full path to check
make sure you have a file located in
/data/user/0/your_proejct_name/app_flutter/Movies/2019-11-08.mp4
code snippet
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
working demo
full code
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:video_player/video_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
String dirPath;
bool loading = false;
Future<String> load_path_video() async {
loading = true;
final Directory extDir = await getApplicationDocumentsDirectory();
setState(() {
dirPath = '${extDir.path}/Movies/2019-11-08.mp4';
print(dirPath);
loading = false;
// if I print ($dirPath) I have /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4
});
}
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
void initState() {
// TODO: implement initState
load_path_video();
super.initState();
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
body: ListView(
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
child: loading
? CircularProgressIndicator()
: NetworkPlayerLifeCycle(
'$dirPath', // with the String dirPath I have error but if I use the same path but write like this /data/user/0/com.XXXXX.flutter_video_test/app_flutter/Movies/2019-11-08.mp4 it's ok ... why ?
(BuildContext context, VideoPlayerController controller) =>
AspectRatioVideo(controller)),
),
],
),
);
}
}
class VideoPlayPause extends StatefulWidget {
VideoPlayPause(this.controller);
final VideoPlayerController controller;
#override
State createState() {
return _VideoPlayPauseState();
}
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() {
listener = () {
setState(() {});
};
}
FadeAnimation imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
VoidCallback listener;
VideoPlayerController get controller => widget.controller;
#override
void initState() {
super.initState();
controller.addListener(listener);
controller.setVolume(1.0);
controller.play();
}
#override
void deactivate() {
controller.setVolume(0.0);
controller.removeListener(listener);
super.deactivate();
}
#override
Widget build(BuildContext context) {
final List<Widget> children = <Widget>[
GestureDetector(
child: VideoPlayer(controller),
onTap: () {
if (!controller.value.initialized) {
return;
}
if (controller.value.isPlaying) {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.pause, size: 100.0));
controller.pause();
} else {
imageFadeAnim =
FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0));
controller.play();
}
},
),
Align(
alignment: Alignment.bottomCenter,
child: VideoProgressIndicator(
controller,
allowScrubbing: true,
),
),
Center(child: imageFadeAnim),
Center(
child: controller.value.isBuffering
? const CircularProgressIndicator()
: null),
];
return Stack(
fit: StackFit.passthrough,
children: children,
);
}
}
class FadeAnimation extends StatefulWidget {
FadeAnimation(
{this.child, this.duration = const Duration(milliseconds: 500)});
final Widget child;
final Duration duration;
#override
_FadeAnimationState createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController =
AnimationController(duration: widget.duration, vsync: this);
animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
animationController.forward(from: 0.0);
}
#override
void deactivate() {
animationController.stop();
super.deactivate();
}
#override
void didUpdateWidget(FadeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {
animationController.forward(from: 0.0);
}
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return animationController.isAnimating
? Opacity(
opacity: 1.0 - animationController.value,
child: widget.child,
)
: Container();
}
}
typedef Widget VideoWidgetBuilder(
BuildContext context, VideoPlayerController controller);
abstract class PlayerLifeCycle extends StatefulWidget {
PlayerLifeCycle(this.dataSource, this.childBuilder);
final VideoWidgetBuilder childBuilder;
final String dataSource;
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// a data source from the network.
class NetworkPlayerLifeCycle extends PlayerLifeCycle {
NetworkPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
#override
_NetworkPlayerLifeCycleState createState() => _NetworkPlayerLifeCycleState();
}
/// A widget connecting its life cycle to a [VideoPlayerController] using
/// an asset as data source
class AssetPlayerLifeCycle extends PlayerLifeCycle {
AssetPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder)
: super(dataSource, childBuilder);
#override
_AssetPlayerLifeCycleState createState() => _AssetPlayerLifeCycleState();
}
abstract class _PlayerLifeCycleState extends State<PlayerLifeCycle> {
VideoPlayerController controller;
#override
/// Subclasses should implement [createVideoPlayerController], which is used
/// by this method.
void initState() {
super.initState();
controller = createVideoPlayerController();
controller.addListener(() {
if (controller.value.hasError) {
print(controller.value.errorDescription);
}
});
controller.initialize();
controller.setLooping(true);
controller.play();
}
#override
void deactivate() {
super.deactivate();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.childBuilder(context, controller);
}
VideoPlayerController createVideoPlayerController();
}
class _NetworkPlayerLifeCycleState extends _PlayerLifeCycleState {
#override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.network(widget.dataSource);
}
}
class _AssetPlayerLifeCycleState extends _PlayerLifeCycleState {
#override
VideoPlayerController createVideoPlayerController() {
return VideoPlayerController.asset(widget.dataSource);
}
}
/// A filler card to show the video in a list of scrolling contents.
Widget buildCard(String title) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const Icon(Icons.airline_seat_flat_angled),
title: Text(title),
),
// TODO(jackson): Remove when deprecation is on stable branch
// ignore: deprecated_member_use
ButtonTheme.bar(
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('BUY TICKETS'),
onPressed: () {
/* ... */
},
),
FlatButton(
child: const Text('SELL TICKETS'),
onPressed: () {
/* ... */
},
),
],
),
),
],
),
);
}
class VideoInListOfCards extends StatelessWidget {
VideoInListOfCards(this.controller);
final VideoPlayerController controller;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
buildCard("Item a"),
buildCard("Item b"),
buildCard("Item c"),
buildCard("Item d"),
buildCard("Item e"),
buildCard("Item f"),
buildCard("Item g"),
Card(
child: Column(children: <Widget>[
Column(
children: <Widget>[
const ListTile(
leading: Icon(Icons.cake),
title: Text("Video video"),
),
Stack(
alignment: FractionalOffset.bottomRight +
const FractionalOffset(-0.1, -0.1),
children: <Widget>[
AspectRatioVideo(controller),
Image.asset('assets/flutter-mark-square-64.png'),
]),
],
),
])),
buildCard("Item h"),
buildCard("Item i"),
buildCard("Item j"),
buildCard("Item k"),
buildCard("Item l"),
],
);
}
}
class AspectRatioVideo extends StatefulWidget {
AspectRatioVideo(this.controller);
final VideoPlayerController controller;
#override
AspectRatioVideoState createState() => AspectRatioVideoState();
}
class AspectRatioVideoState extends State<AspectRatioVideo> {
VideoPlayerController get controller => widget.controller;
bool initialized = false;
VoidCallback listener;
#override
void initState() {
super.initState();
listener = () {
if (!mounted) {
return;
}
if (initialized != controller.value.initialized) {
initialized = controller.value.initialized;
setState(() {});
}
};
controller.addListener(listener);
}
#override
Widget build(BuildContext context) {
if (initialized) {
return Center(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayPause(controller),
),
);
} else {
return Container();
}
}
}
Is it possible to detect when a Drawer is open so that we can run some routine to update its content?
A typical use case I have would be to display the number of followers, likers... and for this, I would need to poll the server to get this information, then to display it.
I tried to implement a NavigatorObserver to catch the moment when the Drawer is made visible/hidden but the NavigatorObserver does not detect anything about the Drawer.
Here is the code linked to the NavigatorObserver:
import 'package:flutter/material.dart';
typedef void OnObservation(Route<dynamic> route, Route<dynamic> previousRoute);
typedef void OnStartGesture();
class NavigationObserver extends NavigatorObserver {
OnObservation onPushed;
OnObservation onPopped;
OnObservation onRemoved;
OnObservation onReplaced;
OnStartGesture onStartGesture;
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (onPushed != null) {
onPushed(route, previousRoute);
}
}
#override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
if (onPopped != null) {
onPopped(route, previousRoute);
}
}
#override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
if (onRemoved != null)
onRemoved(route, previousRoute);
}
#override
void didReplace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
if (onReplaced != null)
onReplaced(newRoute, oldRoute);
}
#override
void didStartUserGesture() {
if (onStartGesture != null){
onStartGesture();
}
}
}
and the initialization of this observer
void main(){
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
final NavigationObserver _observer = new NavigationObserver()
..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
print('** pushed route: $route');
}
..onPopped = (Route<dynamic> route, Route<dynamic> previousRoute) {
print('** poped route: $route');
}
..onReplaced = (Route<dynamic> route, Route<dynamic> previousRoute) {
print('** replaced route: $route');
}
..onStartGesture = () {
print('** on start gesture');
};
#override
void initState(){
super.initState();
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Title',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SplashScreen(),
routes: <String, WidgetBuilder> {
'/splashscreen': (BuildContext context) => new SplashScreen(),
},
navigatorObservers: <NavigationObserver>[_observer],
);
}
}
Thanks for your help.
This answer is old now. Please see #dees91's answer.
Detecting & Running Functions When Drawer Is Opened / Closed
Run initState() when open drawer by any action.
Run dispose() when close drawer by any action.
class MyDrawer extends StatefulWidget {
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
#override
void initState() {
super.initState();
print("open");
}
#override
void dispose() {
print("close");
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
Text("test1"),
Text("test2"),
Text("test3"),
],
),
);
}
}
State Management Considerations
If you are altering state with these functions to rebuild drawer items, you may encounter the error: Unhandled Exception: setState() or markNeedsBuild() called during build.
This can be handled by using the following two functions in initState() source
Option 1
WidgetsBinding.instance.addPostFrameCallback((_){
// Add Your Code here.
});
Option 2
SchedulerBinding.instance.addPostFrameCallback((_) {
// add your code here.
});
Full Example of Option 1
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// Your Code Here
});
}
As https://github.com/flutter/flutter/pull/67249 is already merged and published with Flutter 2.0 here is proper way to detect drawer open/close:
Scaffold(
onDrawerChanged: (isOpened) {
//todo what you need for left drawer
},
onEndDrawerChanged: (isOpened) {
//todo what you need for right drawer
},
)
Best solution
ScaffoldState has a useful method isDrawerOpen which provides the status of open/close.
Example: Here on the back press, it first checks if the drawer is open, if yes then first it will close before exit.
/// create a key for the scaffold in order to access it later.
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(context) {
return WillPopScope(
child: Scaffold(
// assign key (important)
key: _scaffoldKey,
drawer: SideNavigation(),
onWillPop: () async {
// drawer is open then first close it
if (_scaffoldKey.currentState.isDrawerOpen) {
Navigator.of(context).pop();
return false;
}
// we can now close the app.
return true;
});}
I think one simple solution is to override the leading property of your AppBar so you can have access when the menu icon is pressed an run your API calls based on that.
Yet I may have misunderstood your question because with the use case you provided, you usually need to manage it in a way that you can listen to any change which will update the value automatically so I am not sure what are you trying to trigger when the drawer is open.
Anyway here is the example.
class DrawerExample extends StatefulWidget {
#override
_DrawerExampleState createState() => new _DrawerExampleState();
}
class _DrawerExampleState extends State<DrawerExample> {
GlobalKey<ScaffoldState> _key = new GlobalKey<ScaffoldState>();
int _counter =0;
_handleDrawer(){
_key.currentState.openDrawer();
setState(() {
///DO MY API CALLS
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _key,
appBar: new AppBar(
title: new Text("Drawer Example"),
centerTitle: true,
leading: new IconButton(icon: new Icon(
Icons.menu
),onPressed:_handleDrawer,),
),
drawer: new Drawer(
child: new Center(
child: new Text(_counter.toString(),style: Theme.of(context).textTheme.display1,),
),
),
);
}
}
You can simply use onDrawerChanged for detecting if the drawer is opened or closed in the Scaffold widget.
Property :
{void Function(bool)? onDrawerChanged}
Type: void Function(bool)?
Optional callback that is called when the Scaffold.drawer is opened or closed.
Example :
#override
Widget build(BuildContext context) {
return Scaffold(
onDrawerChanged:(val){
if(val){
setState(() {
//foo bar;
});
}else{
setState(() {
//foo bar;
});
}
},
drawer: Drawer(
child: Container(
)
));
}
Unfortunately, at the moment there is no readymade solution.
You can use the dirty hack for this: to observe the visible position of the Drawer.
For example, I used this approach to synchronise the animation of the icon on the button and the location of the Drawer box.
The code that solves this problem you can see below:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class DrawerListener extends StatefulWidget {
final Widget child;
final ValueChanged<FractionalOffset> onPositionChange;
DrawerListener({
#required this.child,
this.onPositionChange,
});
#override
_DrawerListenerState createState() => _DrawerListenerState();
}
class _DrawerListenerState extends State<DrawerListener> {
GlobalKey _drawerKey = GlobalKey();
int taskID;
Offset currentOffset;
#override
void initState() {
super.initState();
_postTask();
}
_postTask() {
taskID = SchedulerBinding.instance.scheduleFrameCallback((_) {
if (widget.onPositionChange != null) {
final RenderBox box = _drawerKey.currentContext?.findRenderObject();
if (box != null) {
Offset newOffset = box.globalToLocal(Offset.zero);
if (newOffset != currentOffset) {
currentOffset = newOffset;
widget.onPositionChange(
FractionalOffset.fromOffsetAndRect(
currentOffset,
Rect.fromLTRB(0, 0, box.size.width, box.size.height),
),
);
}
}
}
_postTask();
});
}
#override
void dispose() {
SchedulerBinding.instance.cancelFrameCallbackWithId(taskID);
if (widget.onPositionChange != null) {
widget.onPositionChange(FractionalOffset(1.0, 0));
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
key: _drawerKey,
child: widget.child,
);
}
}
If you are only interested in the final events of opening or closing the box, it is enough to call the callbacks in initState and dispose functions.
there is isDrawerOpen property in ScaffoldState so you can check whenever you want to check.
create a global key ;
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
assign it to scaffold
Scaffold(
key: scaffoldKey,
appBar: ..)
check where ever in the app
bool opened =scaffoldKey.currentState.isDrawerOpen;
By the time this question was being posted it was a bit trick to accomplish this. But from Flutter 2.0, it is pretty easy. Inside your Scaffold you can detect both the right drawer and the left drawer as follows.
#override
Widget build(BuildContext context) {
return Scaffold(
onDrawerChanged: (isOpened) {
*//Left drawer, Your code here,*
},
onEndDrawerChanged: (isOpened) {
*//Right drawer, Your code here,*
},
);
}
You can use Scaffold.of(context) as below to detect the Drawer status :
NOTE: you must put your code in the Builder widget to use the context which contains scaffold.
Builder(
builder: (context) => IconButton(
icon: Icon(
Icons.menu,
color: getColor(context, opacity.value),
),
onPressed: () {
if (Scaffold.of(context).isDrawerOpen) {
Scaffold.of(context).closeDrawer();
} else {
Scaffold.of(context).openDrawer();
}
},
),
),