Updating a widget and issue with setStat - flutter

I am currently working on this tutorial in which transfers a variable "myVariable" an external file to another.
Link:
https://dev.to/lucianojung/global-variable-access-in-flutter-3ijm
I modified my code so that the variable "myVariable" is usable in the myservice.dart file, it works, but the problem is that I cannot dynamically update my text widget "widget.myVariable", it updates only when I do a "hot reaload" I think we should add a setStat, but I do not know how to implement it despite several attempts.
Thanks for your help
myservice.dart
import 'package:flutter/material.dart';
import 'main.dart';
class MyService extends StatefulWidget {
//Passed 'myVariable' ///////////////////////////////////////////////////
static final MyService _instance = MyService._internal();
// passes the instantiation to the _instance object
factory MyService() => _instance;
//initialize variables in here
MyService._internal() {
_myVariable = 0;
}
int _myVariable;
//short getter for my variable
int get myVariable => _myVariable;
//short setter for my variable
set myVariable(int value) => myVariable = value;
void incrementMyVariable() => _myVariable++;
/////////////////////////////////////////////////////
#override
_MyServiceState createState() => _MyServiceState();
}
class _MyServiceState extends State<MyService> {
/* i try this but no result
_displayCounter() {
setState(() {
widget.myVariable;
});
}
*/
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
width: 250,
height: 90,
child: Text(
// '${_displayCounter()}',
'${widget.myVariable}',
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'myservice.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Service Demo App'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MyService _myService = MyService();
_incrementCounter() {
setState(() {
_myService.incrementMyVariable();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
MyService(), //myservice.dart
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

We use a singleton when we need to manage a shared resource.
You can read more here.
What you are trying here, is to use the singleton class as a 'widget'.
It is evident that you are trying to use myVariable. Perhaps you can make a separate widget that meets this requirement.
1 -> Edit main.dart : I will make use of _myService object and pass it to Service() where it is supposed to show the count.
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
/* Now because of setState(), the build method will be called
as many times as the counter increases, which will call
Service() with the MyService instance below
*/
Service(_myService), // ------ service_widget.dart
]
2 -> Leave myservice.dart as it is (as given in the tutorial).
3 -> service_widget.dart will be as follows:
import 'package:flutter/material.dart';
import './myservice.dart';
class Service extends StatelessWidget {
final MyService myService;
Service(this.myService); // making use of MyService instance
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
width: double.infinity,
child: Text(
'${myService.myVariable}', // Access myVariable here
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
);
}
}
Now it works fine!
Though you can see the full code here, I highly recommend you play around some more.
main.dart
myservice.dart
service_widget.dart

Related

Error Missing concrete implementation of 'StatefulWidget.createState'. Try implementing the missing method, or make the class abstract

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Container(
height: 200,
width: 100,
color: Colors.yellow,
),
),
);
}
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("asset/image/Bestone.jpg")))));
}
}
No matter what I'm using, I have a problem when I'm trying to run or code. I get this error.
Missing concrete implementation of 'StatefulWidget.createState'. Try implementing the missing method, or make the class abstract for the MyApp ()
StatefulWidget requires override method createState So you must override that method. Remove the build method and specify the following line inside MyApp
#override
_MyHomePageState createState() => _MyHomePageState();
Full example with proper StateFulWidget
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

How to pass variables between 2 different files and manipulate them

I am learning flutter, and I would like to know how to pass a variable or a method between 2 different files (to add additional widgets).
In my example, I took the code provided by flutter when we create a new project, to this code, I added a second file called "second.dart" in which I get the variable "_counter" in the file main "main.dart" which I multiply by 10.
main.dart
import 'package:flutter/material.dart';
import 'second.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
Container(
child: Second(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
second.dart
import 'package:flutter/material.dart';
import 'main.dart';
class Second extends StatefulWidget {
#override
_SecondState createState() => _SecondState();
}
class _SecondState extends State<Second> {
int counter10 = _MyHomePageState._counter * 10;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
width: 200,
height: 300,
child: Text('My increment * 10 : $counter10 '),
);
}
}
however, I have this error."_MyHomePageState" is highlighted in red.
lib/second.dart:11:18: Error: Getter not found: '_MyHomePageState'.
var counter10 =_MyHomePageState._counter * 10;
Thank you for your help
See this isn't the way how we pass variables to the other files or other widgets. To create a widget you need to choose between stateful or stateless if you want to manipulate state from inside of the Second class declare it as a stateful but in your case you need to have a stateless widget.
For Example this:
class Second extends StatelessWidget {
final int counter;
const Second({Key key, this.counter}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
width: 200,
height: 300,
child: Text('My increment * 10 : ${counter * 10} '));
}
}
And Pass this variable from your first that is like this :
import 'package:flutter/material.dart';
import 'second.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
Container(
child: Second(counter:_counter),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Note : You can share normal variables by referring them across classes but they shouldn't be private eg have an _ in front of them. Another thing even if you share your variable it wont have any effect on the Second Page directly as flutter doesn't know that it is changing and rather just treat it as an instance.
You can share static data using classes:
example
class AppStrings {
static Color appColor = Colors.blue;
}
And later refer to it as AppStrings.appColor
There are some points that you should do in your codes:
when we use underscore as the first character of the variable name, it means that this variable is private! so you can not use int counter10 = _MyHomePageState._counter * 10; in the second widget!
if you want to pass variables to the child widget, you need to create a constructor as follow:
class Second extends StatefulWidget {
int counter;
Second(this.counter);
#override
_SecondState createState() => _SecondState();
}
now you can pass _countre variable to the Second widget:
Container(
child: Second(_counter),
),
please let me know if there is any problem or error.

Flutter: How to change the state of a variable in another dart file?

I am currently working on an app; I want to change the value of a String which is declared in another dart file and then pass that changed state to the stateful widget.
I.E;
I create a file called as "Body.dart" file where I have declared a String called as 'scale' who's value initially is "Empty".
Later when a button in another dart file "scale_button" is pressed, I want to assign the string scale = "Hello" in my Body.dart file. So that the stateful widget also displays the same on the screen.
You can use provider(or any other state management) package in that case. In yaml file add, provider: ^4.3.2+4
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
StringProvider _stringProvider;
#override
void initState() {
super.initState();
_stringProvider = Provider.of<StringProvider>(context, listen: false);
}
void updateString() {
_stringProvider.setString('hai');
}
#override
Widget build(BuildContext context) {
StringProvider _stringProvider = Provider.of<StringProvider>(context);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
_stringProvider.str,
style: TextStyle(
fontSize: 22,
),
),
),
RaisedButton(
onPressed: updateString,
child: Text('Click'),
),
],
),
),
);
}
}
// class for storing data(StringProvider.dart)
import 'package:flutter/material.dart';
class StringProvider extends ChangeNotifier { // create a common file for data
String _str = 'hello';
String get str => _str;
void setString(String st) {
_str = st;
notifyListeners();
}
}
When you create a new Flutter project the sample code of the counter shows you how to do this. Check out the comments in the next code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// here is passing a String to MyHomePage.
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
// And here you can see how to make the widget wait for a variable
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
Full code of Sample Counter App
import 'package:flutter/material.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(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Flutter: scoped model access in StatefulWidget

I have scoped model lib/scoped_models/main.dart:
import 'package:scoped_model/scoped_model.dart';
class MainModel extends Model {
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
notifyListeners();
}
void setCount(int value) {
_count = value;
notifyListeners();
}
And very simple app lib/main.dart:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_m_test/scoped_models/main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: MainModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
)
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final MainModel _model = MainModel();
void initState() {
super.initState();
// _model.incrementCount(); // <-- doesn't work !!!
}
void _incrementCounter() {
setState(() {
// _model.incrementCount(); // <-- doesn't work !!!
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
}
)
],
),
),
floatingActionButton: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return FloatingActionButton(
onPressed: () {
model.incrementCount(); // <-- only this works !!!
// _incrementCounter(); // <-- doesn't work !!!
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
)
);
}
}
The problem that I can't access MainModel outside of ScopedModelDescendant widget.
How to call MainModel methods at the beginning of _MyHomePageState class?
I believe it is possible because I don't want to keep all logic just in MainModel class and call every method in ScopedModelDescendant widget because it would be very inconvenient if there were many nested widgets.
So, how to get access to scoped model in StatefulWidget?
Use Scoped Model as provider
add ScopedModel just before the widget which use it (MyHomePage)
use ScopedModel.of<MainModel>(context) to control the model
use ScopedModelDescendant<MainModel> to listen the model
The advantage of using this:
You can access the same model in the descendants and share data easily
rebuild widget as small as possible (only ScopedModelDescendant part will be rebuilt)
code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ScopedModel<MainModel>(
model: MainModel(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
ScopedModel.of<MainModel>(context).incrementCount();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ScopedModelDescendant<MainModel>(
builder: (context,child, model){
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Put MainModel as a Singleton
As your solution, you create MainModel once and make it final. This can be more simple like below:
MainModel
final MainModel mainModel = MainModel();
class MainModel{
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
}
void setCount(int value) {
_count = value;
}
}
MyHomePage
MainModel even no need to extend Model or use notifyListeners becaue the widget use setState to rebuild
code:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
mainModel.incrementCount();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${mainModel.count}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
After watching into my code for a while I realized how stupid simple it was to fix.
So, obviously there should be just one instance of MainModel() for all widgets and files of the project and for convenience it should be placed in scoped model file lib/scoped_models/main.dart like this:
import 'package:scoped_model/scoped_model.dart';
final MainModel mainModel = MainModel(); // <-- create instance once for all files which require scoped model import
class MainModel extends Model {
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
notifyListeners();
}
void setCount(int value) {
_count = value;
notifyListeners();
}
And then you can use mainModel instance anywhere you import the model import 'package:<app_name>/scoped_models/main.dart';
So that, this code will be valid lib/main.dart:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_m_test/scoped_models/main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: mainModel, // <-- instance of model from 'lib/<app_name>/scoped_models/main.dart'
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
)
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
mainModel.incrementCount(); // <-- now it works !!!
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
}
)
],
),
),
floatingActionButton: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return FloatingActionButton(
onPressed: () {
// model.incrementCount(); // <-- works !!!
_incrementCounter(); // <-- now it's working too !!!
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
)
);
}
}
Despite that fact that is seems reasonable, it can be overwhelming as well for the first time due to lack of examples.

Flutter: Detect rebuild of any widget which is not visible on screen but is in the widget tree

Summary:
As showing a page/route using the Navigator, a new branch is created from the nearest MaterialApp parent. Meaning both pages (Main & New) will be in memory and will rebuild if they are listening to the same ChangeNotifier.
I am having trouble finding out which widget is on-screen currently visible to the user.
I need this to handle a scenario to skip performing asynchronous or long processes with some side effects, from a widget that might be in the widget tree but currently not visible.
Note: The sample code given here represents the basic architecture of the app I am currently working on, but reproduces the exact problem.
I am having this problem with a very different and complex widget tree that I have in my app, executing the doLongProcess() from a widget that is not visible on the screen. Also doLongProcess() changes some common property in my app which causes an issue, as any background widget can modify the details which are visible on the other widget.
I am looking for a solution to this issue, if there's any other way to achieve the goal except finding which widget is on the screen then please let me know that as well.
My final goal is to allow the long process to be executed from only the visible widget(s).
Please run the app once, to understand the following details properly.
Note 2:
I have tried to use mounted property of the state to determine if it can be used or not but it shows true for both widgets (MainPage TextDisplay and NewPage TextDisplay)
Let me know in the comments if more details or I missed something which is required.
Use the following sample code with provider dependency included for reproducing the problem:
// add in pubspec.yaml: provider: ^4.3.2+1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
print('MainPage: build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'MainPage TextDisplay',
),
SizedBox(
height: 20,
),
RaisedButton(
child: Text('Open New Page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewPage(),
)),
),
],
),
),
);
}
}
class TextDisplay extends StatefulWidget {
final String name;
const TextDisplay({Key key, #required this.name}) : super(key: key);
#override
_TextDisplayState createState() => _TextDisplayState();
}
class _TextDisplayState extends State<TextDisplay> {
#override
Widget build(BuildContext context) {
return Container(
child: ChangeNotifierProvider.value(
value: dataHolder,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text(widget.name)),
SizedBox(
height: 20,
),
Consumer<DataHolder>(
builder: (context, holder, child) {
// need to detect if this widget is on the screen,
// only then we should go ahead with this long process
// otherwise we should skip this long process
doLongProcess(widget.name);
return Text(holder.data);
},
),
RaisedButton(
child: Text('Randomize'),
onPressed: () => randomizeData(),
),
],
),
),
);
}
void doLongProcess(String name) {
print('$name: '
'Doing a long process using the new data, isMounted: $mounted');
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('NewPage: build');
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'NewPage TextDisplay',
),
],
),
),
);
}
}
/////////////////// Data Holder Class and methods ///////////////////
class DataHolder extends ChangeNotifier {
String _data;
String get data => _data ?? 'Nothing to show, Yet!';
setData(String newData) {
print('\n new data found: $newData');
_data = newData;
notifyListeners();
}
}
final dataHolder = DataHolder();
randomizeData() {
int mills = DateTime.now().millisecondsSinceEpoch;
dataHolder.setData(mills.toString());
}
Posting solution for others to refer.
Refer to this flutter plugin/package:
https://pub.dev/packages/visibility_detector
The solution code:
// add in pubspec.yaml: provider: ^4.3.2+1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
print('MainPage: build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'MainPage TextDisplay',
),
SizedBox(
height: 20,
),
RaisedButton(
child: Text('Open New Page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewPage(),
)),
),
],
),
),
);
}
}
class TextDisplay extends StatefulWidget {
final String name;
const TextDisplay({Key key, #required this.name}) : super(key: key);
#override
_TextDisplayState createState() => _TextDisplayState();
}
class _TextDisplayState extends State<TextDisplay> {
/// this holds the latest known status of the widget's visibility
/// if [true] then the widget is fully visible, otherwise it is false.
///
/// Note: it is also [false] if the widget is partially visible since we are
/// only checking if the widget is fully visible or not
bool _isVisible = true;
#override
Widget build(BuildContext context) {
return Container(
child: ChangeNotifierProvider.value(
value: dataHolder,
/// This is the widget which identifies if the widget is visible or not
/// To my suprise this is an external plugin which is developed by Google devs
/// for the exact same purpose
child: VisibilityDetector(
key: ValueKey<String>(widget.name),
onVisibilityChanged: (info) {
// print('\n ------> Visibility info:'
// '\n name: ${widget.name}'
// '\n visibleBounds: ${info.visibleBounds}'
// '\n visibleFraction: ${info.visibleFraction}'
// '\n size: ${info.size}');
/// We use this fraction value to determine if the TextDisplay widget is
/// fully visible or not
/// range for fractional value is: 0 <= visibleFraction <= 1
///
/// Meaning we can also use fractional values like, 0.25, 0.3 or 0.5 to
/// find if the widget is 25%, 30% or 50% visible on screen
_isVisible = info.visibleFraction == 1;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text(widget.name)),
SizedBox(
height: 20,
),
Consumer<DataHolder>(
builder: (context, holder, child) {
/// now that we have the status of the widget's visiblity
/// we can skip the long process when the widget is not visible.
if (_isVisible) {
doLongProcess(widget.name);
}
return Text(holder.data);
},
),
RaisedButton(
child: Text('Randomize'),
onPressed: () => randomizeData(),
),
],
),
),
),
);
}
void doLongProcess(String name) {
print('\n ============================ \n');
print('$name: '
'Doing a long process using the new data, isMounted: $mounted');
final element = widget.createElement();
print('\n name: ${widget.name}'
'\n element: $element'
'\n owner: ${element.state.context.owner}');
print('\n ============================ \n');
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('NewPage: build');
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'NewPage TextDisplay',
),
],
),
),
);
}
}
/////////////////// Data Holder Class and methods ///////////////////
class DataHolder extends ChangeNotifier {
String _data;
String get data => _data ?? 'Nothing to show, Yet!';
setData(String newData) {
print('\n new data found: $newData');
_data = newData;
notifyListeners();
}
}
final dataHolder = DataHolder();
randomizeData() {
int mills = DateTime.now().millisecondsSinceEpoch;
dataHolder.setData(mills.toString());
}