Right now, a widget only has initeState() that gets triggered the very first time a widget is created, and dispose(), which gets triggered when the widget is destroyed. Is there a method to detect when a widget comes back to the foreground? and when a widget is about to go to the background because another widget just was foregrounded?
It's the equivalent of onResume and onPause being triggered for Android, and viewWillAppear and viewWillDisappear for ios
There is an abstract class caller WidgetsBindingObserver
https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html
in
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_notification = state;
});
}
there is the "state", can be manage as
switch(state) {
case AppLifecycleState.resumed:
// Handle this case
break;
case AppLifecycleState.inactive:
// Handle this case
break;
case AppLifecycleState.paused:
// Handle this case
break;
case AppLifecycleState.suspending:
// Handle this case
break;
}
This is a full example demonstrating how to properly handle things, to test this, press home button and resume the app, you shall see didChangeAppLifecycleState is getting called.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
// Add the observer.
WidgetsBinding.instance!.addObserver(this);
}
#override
void dispose() {
// Remove the observer
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
// These are the callbacks
switch (state) {
case AppLifecycleState.resumed:
// widget is resumed
break;
case AppLifecycleState.inactive:
// widget is inactive
break;
case AppLifecycleState.paused:
// widget is paused
break;
case AppLifecycleState.detached:
// widget is detached
break;
}
}
#override
Widget build(BuildContext context) => Scaffold();
}
The most common case where you'd want to do this is if you have an animation running and you don't want to consume resources in the background. In that case, you should extend your State with TickerProviderStateMixin and use your State as the vsync argument for the AnimationController. Flutter will take care of only calling the animation controller's listeners when your State is visible.
If you want the States that live in your PageRoute to be disposed when the PageRoute is obscured by other content, you can pass a maintainState argument of false to your PageRoute constructor. If you do this, your State will reset itself (and its children) when it's hidden and will have to re-construct itself in initState using the properties passed in as constructor arguments to its widget. You can use a model or controller class, or PageStorage, to hold the user's progress information if you don't want a complete reset.
Here is a sample app that demonstrates these concepts.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/') {
return new MaterialPageRoute<Null>(
settings: settings,
builder: (_) => new MyApp(),
maintainState: false,
);
}
return null;
}
));
}
class MyApp extends StatefulWidget {
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
print("initState was called");
_controller = new AnimationController(vsync: this)
..repeat(min: 0.0, max: 1.0, period: const Duration(seconds: 1))
..addListener(() {
print('animation value ${_controller.value}');
});
super.initState();
}
#override
void dispose() {
print("dispose was called");
_controller.dispose();
super.dispose();
}
int _counter = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('home screen')
),
body: new Center(
child: new RaisedButton(
onPressed: () {
setState(() {
_counter++;
});
},
child: new Text('Button pressed $_counter times'),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.remove_red_eye),
onPressed: () {
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) {
return new MySecondPage(counter: _counter);
},
));
},
),
);
}
}
class MySecondPage extends StatelessWidget {
MySecondPage({ this.counter });
final int counter;
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Certificate of achievement'),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
new Icon(Icons.developer_mode, size: 200.0),
new Text(
'Congrats, you clicked $counter times.',
style: Theme.of(context).textTheme.title,
textAlign: TextAlign.center,
),
new Text(
'All your progress has now been lost.',
style: Theme.of(context).textTheme.subhead,
textAlign: TextAlign.center,
),
],
),
);
}
}
I am a little late but came with perfect solution for those who may be looking for it in the future. the Navigator.push() is actually a Future. it means it has then() callback function. so the then() will be called after you call Navigator.pop() from the second screen. and even you can send some data from the second screen and access the data in the first screen.
example:
//from Screen A
Navigator.of(context).push(MaterialPageRoute(builder:(context)=>B()))
.then((value)=>{ refresh() });
//in Screen B with data
Navigator.pop(context,[1]);
//or without data
Navigator.pop(context);
so refresh() will be called on resume of Screen A.
I created visibility_aware_state for having something that behaves similar to Android's Activity.onResume(). It also considers pop and push navigation.
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends VisibilityAwareState<Example> {
#override
Widget build(BuildContext context) {
// return your widget
}
#override
void onVisibilityChanged(WidgetVisibility visibility) {
switch(visibility) {
case WidgetVisibility.VISIBLE:
// Like Android's Activity.onResume()
break;
case WidgetVisibility.INVISIBLE:
// Like Android's Activity.onPause()
break;
case WidgetVisibility.GONE:
// Like Android's Activity.onDestroy()
break;
}
super.onVisibilityChanged(visibility);
}
}
Mamnarock your answer is correct but not complete, and the link that you are shared isn't available.
Here is the complete code:
import 'package:flutter/material.dart';
class YourClass extends StatefulWidget {
#override
_YourClassState createState() => _YourClassState();
}
class _YourClassState extends State<YourClass>
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
// Handle this case
break;
case AppLifecycleState.inactive:
// Handle this case
break;
case AppLifecycleState.paused:
// Handle this case
break;
case AppLifecycleState.detached:
// Handle this case
break;
}
}
#override
Widget build(BuildContext context) {
return Container();
}
}
And as TeeTracker mentioned in the comment:
This is an app-level lifecycle, which means when the whole is resumed or inactive, or paused, not the single widget.
Related
I've been working on a Flutter app for a few days now.
I've implemented a NavigationRail for my app. It works pretty well, excepted for one thing.
When I tap on a NavigationRailDestination, it calls the function updateRoute(index) and goes to the required route.
My problem is that, the selectedIndex property udpates, but then, when the new route is loaded, the selectedIndex goes back to 0.
Here is my code :
class SidebarDrawer extends StatefulWidget {
final VoidCallback callbackOnScreenChange;
SidebarDrawer({#required this.callbackOnScreenChange});
#override
_SidebarDrawerState createState() => _SidebarDrawerState();
}
class _SidebarDrawerState extends State<SidebarDrawer> {
int index = 0;
#override
Widget build(BuildContext context) => NavigationRail(
extended: isExtended,
onDestinationSelected: (index) => updateRoute(index),
selectedIndex: index,
leading: IconButton(
icon: Icon(Icons.reorder),
onPressed: () => setState(() => isExtended = !isExtended),
),
destinations: <NavigationRailDestination>[
<Some NavigationRailDestination>
],
);
void updateRoute(index) {
setState(() => this.index = index);
switch (index) {
case 0:
return navigateToScreen(
context, '/home', widget.callbackOnScreenChange);
case 1:
return navigateToScreen(
context, '/orderManager', widget.callbackOnScreenChange);
case 2:
return navigateToScreen(
context, '/manualControl', widget.callbackOnScreenChange);
default:
return navigateToScreen(
context, '/home', widget.callbackOnScreenChange);
}
}
The function navigateToScreen() :
/// Navigate to given widget
void navigateToScreen(BuildContext context, String route,
VoidCallback callbackOnScreenChange) async {
// Call callback on screen change
if (callbackOnScreenChange != null) {
callbackOnScreenChange();
}
// Check the current route
String currentRoute = ModalRoute.of(context).settings.name;
// If the current route is not the same as the new route change the screen
if (currentRoute != route) {
await Navigator.pushNamed(context, route);
}
}
I am new to Flutter, I am struggling a bit to understand how does the state works and updates.
I would really appreciate if you could help me understand it please !
Ok, I've managed to correct it.
In my parent widget (MainScaffold), I pass an index value to my child widget (containing the NavigationRail).
Then in my NavigationRail Widget, instead of having the property :
selectedIndex: index
I set it to selectedIndex: widget.selectedDrawerIndex.
Then on each screen, when I render the main scaffold, I set the required selectedDrawerIndex value.
Here is the code from the sidebar Widget :
class SidebarDrawer extends StatefulWidget {
// Index is used to know which icon is selected
final int selectedDrawerIndex;
final VoidCallback callbackOnScreenChange;
SidebarDrawer({#required this.selectedDrawerIndex, #required this.callbackOnScreenChange});
#override
_SidebarDrawerState createState() => _SidebarDrawerState();
}
class _SidebarDrawerState extends State<SidebarDrawer> {
#override
Widget build(BuildContext context) => NavigationRail(
selectedIndex: widget.selectedDrawerIndex,
leading: IconButton(
icon: Icon(Icons.reorder),
onPressed: () => setState(() => isExtended = !isExtended),
),
// When a destination is selected, the routes are updated
onDestinationSelected: (selectedDrawerIndex) => updateRoute(selectedDrawerIndex),
destinations: <NavigationRailDestination>[
[...some navigation rail destinations...]
],
);
void updateRoute(selectedDrawerIndex) {
// Navigate to a specific screen when a destination is clicked
switch (selectedDrawerIndex) {
case 0:
return navigateToScreen(
context, '/home', widget.callbackOnScreenChange);
case 1:
return navigateToScreen(
context, '/orderManager', widget.callbackOnScreenChange);
case 2:
return navigateToScreen(
context, '/manualControl',
default:
return navigateToScreen(
context, '/home', widget.callbackOnScreenChange);
}
}
}
And then in the main scaffold:
class MainScaffold extends StatelessWidget {
final int selectedDrawerIndex;
final Widget body;
// Callback when a screen change is requested
// This is the current way to make sure a screen calls its dispose method when the screen changes
final VoidCallback callbackOnScreenChange;
MainScaffold(
{#required this.selectedDrawerIndex,
#required this.body,
this.callbackOnScreenChange});
[...some more code...]
}
Then I just need to pass the selectedDrawerIndex as parameter when using the MainScaffold Widget.
It works well but I am willing to improve so if you think about a better solution, do not hesitate to post it.
I'm new to flutter. I needed my app to contain 4 different widgets. Each widget has it own data to read from the server in the initState method. First time the layout is loaded initState is called and gets the data from the server fine. So all is working except that I noticed the initState is called again if I click on non-adjacent tabs.
For example: If I Clicked on Tab 3 then Tab 2, after loading them the first time, the previous state is loaded fine and initState is not called again. However, If I clicked Tab 4 then Tab 1 or Tab 2, after loading them the first time, the initState of both tabs is called again and goes to the server to re-fetch the data.
I tried to use if (this.mounted) in the initState but it is evaluated as true and still fetches data from the server again if tabs aren't selected in the same order.
import 'package:flutter/material.dart';
import 'app_layouts.dart';
void main() {
runApp(MaterialApp(home: HomePage2()));
}
class HomePage2 extends StatefulWidget {
#override
_HomePage2State createState() => _HomePage2State();
}
class _HomePage2State extends State<HomePage2> with SingleTickerProviderStateMixin {
static final List<MyTab> myTabs = [
MyTab(tab: Tab(icon: Icon(Icons.home)), tabView: SimpleTabView()),
MyTab(tab: Tab(icon: Icon(Icons.calendar_today)), tabView: SimpleTab2View()),
MyTab(tab: Tab(icon: Icon(Icons.message)), tabView: SimpleTabView()),
MyTab(tab: Tab(icon: Icon(Icons.note)), tabView: SimpleTab2View()),
];
var _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(length: myTabs.length, vsync: this);
_tabController.addListener(() {
//I added a custom tab controller, as I need to be notified with tab change events
});
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test Tab Issue'),
bottom: TabBar(
tabs: myTabs.map((tab) => tab.tab).toList(),
controller: _tabController,
),
),
body: TabBarView(
children: myTabs.map((tab) => tab.tabView).toList(),
controller: _tabController,
),
);
}
}
class MyTab {
Tab tab;
Widget tabView;
MyTab({this.tab, this.tabView});
}
class SimpleTabView extends StatefulWidget {
#override
_SimpleTabViewState createState() => _SimpleTabViewState();
}
class _SimpleTabViewState extends State<SimpleTabView> with AutomaticKeepAliveClientMixin {
bool isDoingTask = true;
#override
void initState() {
super.initState();
print('initState called ...');
if (this.mounted) {
this.getTask();
}
}
#override
Widget build(BuildContext context) {
super.build(context);
return new Stack(
children: <Widget>[
Text('Tab view'),
Loader(showLoading: isDoingTask),
],
);
}
void getTask() async {
setState(() {
isDoingTask = true;
});
print("${new DateTime.now()} Pause for 3 seconds");
await new Future.delayed(const Duration(seconds: 3));
if (!this.mounted) return null;
setState(() {
isDoingTask = false;
});
}
#override
bool get wantKeepAlive => true;
}
//Exactly the same as SimpleTabView except the class name
class SimpleTab2View extends StatefulWidget {....
I expect the initState method to not be called again since I'm already using with AutomaticKeepAliveClientMixin, and it was called the first time already.
This issue has been fixed on master as mentioned on this GitHub issue thread.
how to make the IconButton icon change when the audio has finished playing
I am showing a simple code which mocks a music being played for 10 seconds and then stops after that and Icon changes
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: SOM());
/*
return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
'home': (context) => SOMain(),
'/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
'/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
});
*/
}
}
class SOM extends StatefulWidget {
#override
_SOMState createState() => _SOMState();
}
class _SOMState extends State<SOM> {
bool isPlaying = false;
IconData musicIcon = Icons.play_arrow;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: IconButton(
icon: Icon(musicIcon),
onPressed: toggleMusic,
)),
);
}
void toggleMusic() {
if (!isPlaying) {
startPlayingMusic();
}
}
void startPlayingMusic() {
setState(() {
isPlaying = true;
musicIcon = Icons.pause;
});
// faking music play for 10 seconds
Timer(Duration(seconds: 10), () {
setState(() {
isPlaying = false;
musicIcon = Icons.play_arrow;
});
});
}
}
Note: The current working does not change states if music is already playing, it just play mocks music play for 10s after button click and ends. Nothing else.
Create PlayerEvents like this:
enum PlayerEvent {
onStartPlaying, onStopPlaying, onPausePlaying
}
And create a BLoC like this:
final playerBloc = PlayerBloc();
class PlayerBloc {
StreamController<PlayerEvent> _playerEventStreamController = StreamController.broadcast();
Stream<PlayerEvent> get playerEventStream => _playerEventStreamController.stream;
dispatch(PlayerEvent event) {
_playerEventStreamController.sink.add(event);
}
dispose(){
_playerEventStreamController.close();
}
}
Wrap your icon with stream builder like this:
StreamBuilder<PlayerEvent>(
stream: playerBloc.playerEventStream,
initialData: PlayerEvent.onStopPlaying,
builder: (context, snapshot) {
switch(snapshot.data) {
case PlayerEvent.onStopPlaying:
return Icon(Icons.play_arrow);
case PlayerEvent.onStartPlaying:
return Icon(Icons.pause);
case PlayerEvent.onPausePlaying:
return Icon(Icons.play_arrow);
}
}
),
Now whenever your music starts or stops playing just call this:
playerBloc.dispatch(PlayerEvent.onStartPlaying);
or
playerBloc.dispatch(PlayerEvent.onStopPlaying);
and your icons will change as per your music.
I'm using flutter video_player(https://pub.dartlang.org/packages/video_player) plugin to play videos. But When I navigate from one page to another in flutter the video is still playing. I'm having the same problem when playing music from any other app(eg: playing music from notification while flutter video is playing). How can I pause it?
Edit:
Second option:
So I came up with another solution. VisibilityDetector
is a `Widget' that can wrap any other Widget and notify when the visible area of the widget changed.
VisibilityDetector(
key: Key("unique key"),
onVisibilityChanged: (VisibilityInfo info) {
debugPrint("${info.visibleFraction} of my widget is visible");
if(info.visibleFraction == 0){
videoPlayerController.pause();
}
else{
videoPlayerController.play();
}
},
child: VideoPlayer());
)
First Option:
By using the RouteObserver and RouteAware your widget will be informed whenever another screen is pushed on top of it, or it's popped back on top.
First, you need to add a RouteObserver to MaterialApp or your Navigator.
RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
navigatorObservers: [routeObserver], //HERE
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
Then you must add the RouteAware mixin to your widget, and subscribe it to the RouteObserver.
class VideoPlayerItem extends StatefulWidget {
final File file;
VideoPlayerItem(this.file);
#override
_VideoPlayerItemState createState() => _VideoPlayerItemState();
}
class _VideoPlayerItemState extends State<VideoPlayerItem> with RouteAware {
VideoPlayerController _videoPlayerController;
#override
void initState() {
super.initState();
_videoPlayerController = VideoPlayerController.file(widget.file);
_videoPlayerController.initialize().then((_) {
if (mounted) {
setState(() {});
_videoPlayerController.play();
}
});
}
#override
void didChangeDependencies() {
routeObserver.subscribe(this, ModalRoute.of(context));//Subscribe it here
super.didChangeDependencies();
}
/// Called when the current route has been popped off.
#override
void didPop() {
print("didPop");
super.didPop();
}
/// Called when the top route has been popped off, and the current route
/// shows up.
#override
void didPopNext() {
print("didPopNext");
_videoPlayerController.play();
super.didPopNext();
}
/// Called when the current route has been pushed.
#override
void didPush() {
print("didPush");
super.didPush();
}
/// Called when a new route has been pushed, and the current route is no
/// longer visible.
#override
void didPushNext() {
print("didPushNext");
_videoPlayerController.pause();
super.didPushNext();
}
#override
Widget build(BuildContext context) {
return _videoPlayerController?.value?.initialized ?? false
? AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
child: VideoPlayer(_videoPlayerController),
)
: Container();
}
#override
void dispose() {
routeObserver.unsubscribe(this); //Don't forget to unsubscribe it!!!!!!
super.dispose();
_videoPlayerController.dispose();
}
}
You have - VideoPlayerController _controller;
Before Navigating to other screen -
_controller.pause(); // add this
Navigator.push(
context, MaterialPageRoute(builder: (BuildContext context) => NextPage()));
OR
if it's a Stateful widget then you can override - dispose() method.
#override
void dispose() {
super.dispose();
_controller.pause();
}
Simple and its work for me, I get this from chewie example code: https://pub.dev/packages/chewie/example
Just on the onTap/onPressed use setState and in this setState make videoController.pause or dispose, whatever you want.
onTap:() {
setState(() {
widget.videoController.pause();
Navigator.of(context)
.pushNamed(YOUR-NAVIGATION-PAGE-NAME);
});
},
I have a parent that contain a listView and a floatingActionButton i would like to hide the floatingActionButton when the user starts scrolling i have managed to do this within the parent widget but this requires the list to be rebuilt each time.
I have moved the floatingActionButton to a separate class so i can update the state and only rebuild that widget the problem i am having is passing the data from the ScrollController in the parent class to the child this is simple when doing it through navigation but seams a but more awkward without rebuilding the parent!
A nice way to rebuild only a child widget when a value in the parent changes is to use ValueNotifier and ValueListenableBuilder. Add an instance of ValueNotifier to the parent's state class, and wrap the widget you want to rebuild in a ValueListenableBuilder.
When you want to change the value, do so using the notifier without calling setState and the child widget rebuilds using the new value.
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
ValueNotifier<bool> _notifier = ValueNotifier(false);
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: () => _notifier.value = !_notifier.value, child: Text('toggle')),
ValueListenableBuilder(
valueListenable: _notifier,
builder: (BuildContext context, bool val, Widget? child) {
return Text(val.toString());
}),
],
);
}
#override
void dispose() {
_notifier.dispose();
super.dispose();
}
}
For optimal performance, you can create your own wrapper around Scaffold that gets the body as a parameter. The body widget will not be rebuilt when setState is called in HideFabOnScrollScaffoldState.
This is a common pattern that can also be found in core widgets such as AnimationBuilder.
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: MyHomePage()));
class MyHomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return HideFabOnScrollScaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, i) => ListTile(title: Text('item $i')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
controller: controller,
);
}
}
class HideFabOnScrollScaffold extends StatefulWidget {
const HideFabOnScrollScaffold({
Key key,
this.body,
this.floatingActionButton,
this.controller,
}) : super(key: key);
final Widget body;
final Widget floatingActionButton;
final ScrollController controller;
#override
State<StatefulWidget> createState() => HideFabOnScrollScaffoldState();
}
class HideFabOnScrollScaffoldState extends State<HideFabOnScrollScaffold> {
bool _fabVisible = true;
#override
void initState() {
super.initState();
widget.controller.addListener(_updateFabVisible);
}
#override
void dispose() {
widget.controller.removeListener(_updateFabVisible);
super.dispose();
}
void _updateFabVisible() {
final newFabVisible = (widget.controller.offset == 0.0);
if (_fabVisible != newFabVisible) {
setState(() {
_fabVisible = newFabVisible;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: widget.body,
floatingActionButton: _fabVisible ? widget.floatingActionButton : null,
);
}
}
Alternatively you could also create a wrapper for FloatingActionButton, but that will probably break the transition.
I think using a stream is more simpler and also pretty easy.
You just need to post to the stream when your event arrives and then use a stream builder to respond to those changes.
Here I am showing/hiding a component based on the focus of a widget in the widget hierarchy.
I've used the rxdart package here but I don't believe you need to. also you may want to anyway because most people will be using the BloC pattern anyway.
import 'dart:async';
import 'package:rxdart/rxdart.dart';
class _PageState extends State<Page> {
final _focusNode = FocusNode();
final _focusStreamSubject = PublishSubject<bool>();
Stream<bool> get _focusStream => _focusStreamSubject.stream;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
_focusStreamSubject.add(_focusNode.hasFocus);
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_buildVeryLargeComponent(),
StreamBuilder(
stream: _focusStream,
builder: ((context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData && snapshot.data) {
return Text("keyboard has focus")
}
return Container();
}),
)
],
),
);
}
}
You can use StatefulBuilder and use its setState function to build widgets under it.
Example:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int count = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
// put widget here that you do not want to update using _setState of StatefulBuilder
Container(
child: Text("I am static"),
),
StatefulBuilder(builder: (_context, _setState) {
// put widges here that you want to update using _setState
return Column(
children: [
Container(
child: Text("I am updated for $count times"),
),
RaisedButton(
child: Text('Update'),
onPressed: () {
// Following only updates widgets under StatefulBuilder as we are using _setState
// that belong to StatefulBuilder
_setState(() {
count++;
});
})
],
);
}),
],
);
}
}