I have a flutter widget that attempts to solve soduku grids. I have class called SodukuSolver which does all the calculations and provides a List<String> of the current results. I call setState to refresh the list, but it does not update the screen.
Below, I'll try to include as much of the relevant code as I can. Full source is at https://github.com/mankowitz/soduku
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: "Soduku solver", home: Soduku());
}
}
class SodukuState extends State<Soduku> {
SodukuSolver ss;
List<String> _list;
int _changes = 0;
int _remaining = 81;
#override
Widget build(BuildContext context) {
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
return Scaffold(
appBar: AppBar(title: Text('Soduku solver'), actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.directions_walk),
onPressed: () {
_iterate();
},
),
]),
body: _buildGrid(),
);
}
Widget _buildGrid() {
return Column(children: <Widget>[
AspectRatio(
aspectRatio: 1.0,
child: Container(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 9,
),
itemBuilder: _buildGridItems,
itemCount: 81,
),
),
),
]);
}
Widget _buildGridItems(BuildContext context, int index) {
return GestureDetector(
child: GridTile(
child: Container(
child: Center(
child: Text(_list[index]),
),
),
),
);
}
void _iterate() {
setState(() {
_changes = ss.iterateSoduku();
_remaining = ss.empties();
_list = ss.getList();
});
}
}
class Soduku extends StatefulWidget {
#override
SodukuState createState() => SodukuState();
}
So the problem is that _iterate() is being called, and I can use the debugger to see that the internal state of SodukuSolver is being updated and it is even passing _list correctly, but the grid on screen doesn't update, even though _changes and _remaining do update.
You are creating new SodukuSolver with same _starting every time the widget builds and then obtaining _list from it. So you are overriding changes from previous iteration.
Looks like SodukuSolver creation should be performed once. You can override initState in SodukuState and initialise SodukuSolver there or initialise it in the same place where it is declared
Just add your code in the initState() method as following
#override
void initState() {
super.initState();
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
}
In your case, your list is not getting updated as setState() method will call your SodukuSolver() and ss.getList(); methods every time. because, setSate() ultimately calls build method to render every time.
So adding it inside your initState method will solve your issue. As it is getting called only once when the screen/route initialises.
Related
Firstly, I want to ask is from which method of Stateful widget should I call state management methods. I need to choice two place init() method or build(). I don't exactly know which method is the appropriate method to call state management methods. Let me try to explain with examples to understand of my question.
I use rxdart for dependency injection, used Stream and build with bloc pattern. Then used global scope instead of single scope. So I build another class that extend InheritedWidget and predefine each of the of(context) from the ancestor widget. Then called bloc methods (state management) from each build() method of their needed UI class (Stateful or stateless).
In here, my problem is when every time I called from build method, some of the function build again and again, unless I need to do. So I fix with another way, that is declare, initialized the bloc class and call respective bloc class function with object from init method of stateful class. That is work really. But some of the article said, don't should call from init method and only should call from build method. I am anxiety with that principles and calling from init method not work as global scope (Ex: that init stream not work for another widgets). How should I do with that? please tell or guide me with something.
Here is my code flow to better understanding. I show with count down example bloc that work like When count down become 0, the button will appear and press again that button count down again and work same process again.
That is state management bloc
class OtpLoginModuleBloc {
bool isHideResendButton = true;
final _buttonTimerController = PublishSubject<bool>();
final _textTimerController = PublishSubject<int>();
Stream<bool> get buttonTimerStream => _buttonTimerController.stream;
Stream<int> get textTimerStream => _textTimerController.stream;
void toAppearResendButtonCountown({required int second}) {
var duration = const Duration(seconds: 1);
Timer.periodic(
duration,
(Timer timer) {
if (second == 0) {
isHideResendButton = !isHideResendButton;
_buttonTimerController.sink.add(isHideResendButton);
timer.cancel();
} else {
second--;
}
_textTimerController.sink.add(second);
},
);
}
}
Here is ancestor class
class _PreModuleState extends State<PreModule> {
#override
Widget build(BuildContext context) {
return LoginModuleProvider(
child: OtpLoginModuleProivder(
child: MaterialApp(
theme: themeData(),
home: const Scaffold(
body: SplashScreen(),
),
onGenerateRoute: RouteGenerator.route,
),
),
);
}
}
Here is provider class for global scope
class OtpLoginModuleProivder extends InheritedWidget {
final OtpLoginModuleBloc loginOtpModuleBloc;
OtpLoginModuleProivder({Key? key, required Widget child})
: loginOtpModuleBloc = OtpLoginModuleBloc(),
super(key: key, child: child);
#override
bool updateShouldNotify(OtpLoginModuleProivder oldWidget) => true;
static OtpLoginModuleBloc of(context) {
return (context.dependOnInheritedWidgetOfExactType<OtpLoginModuleProivder>()
as OtpLoginModuleProivder)
.loginOtpModuleBloc;
}
}
And my problem is here ...
Should I call like that
final OtpLoginModuleBloc otpLoginModuleBloc = OtpLoginModuleBloc();
#override
void initState() {
otpLoginModuleBloc.toAppearResendButtonCountown(second: otpCountingTime);
super.initState();
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
Column(
children: [
const OtpLoginHeader(),
const SizedBox(
height: margin50,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: margin100),
child: buildPinCodeTextField(context, otpLoginModuleBloc),
),
const SizedBox(
height: margin30,
),
StreamBuilder(
initialData: otpCountingTime,
stream: otpLoginModuleBloc.textTimerStream,
builder: (context, AsyncSnapshot snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: margin80),
child: snapshot.data == 0
? sendOtpButton(otpLoginModuleBloc, otpCountingTime)
: Text('Request new OTP in ${snapshot.data}'),
);
},
),
],
),
],
);
}
OR
Widget build(BuildContext context) {
final otpLoginModuleBloc = OtpLoginModuleProivder.of(context);
otpLoginModuleBloc.toAppearResendButtonCountown(second: otpCountingTime);
return Stack(
children: [
Column(
children: [
const OtpLoginHeader(),
const SizedBox(
height: margin50,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: margin100),
child: buildPinCodeTextField(context, otpLoginModuleBloc),
),
const SizedBox(
height: margin30,
),
StreamBuilder(
initialData: otpCountingTime,
stream: otpLoginModuleBloc.textTimerStream,
builder: (context, AsyncSnapshot snapshot) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: margin80),
child: snapshot.data == 0
? sendOtpButton(otpLoginModuleBloc, otpCountingTime)
: Text('Request new OTP in ${snapshot.data}'),
);
},
),
],
),
],
);
}
That is my all of my question...
Please help me and guide me ...
Ofc you should go for the latter.
use like this. This way, you can use _otpLoginModuleBloc within the class.
class _ExampleWidgetState extends State<ExampleWidget> {
late OtpLoginModuleBloc _otpLoginModuleBloc
#override
Widget build(BuildContext context) {
_otpLoginModuleBloc = OtpLoginModuleProivder.of(context);
return Container();
}
}
In addition, if you need to run the otpLoginModuleBloc.toAppearResendButtonCountown(second: otpCountingTime); only ONCE, then use below.
class ExampleWidget extends StatefulWidget {
const ExampleWidget({super.key});
#override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
late OtpLoginModuleBloc _otpLoginModuleBloc
final _otpCountingTime = 1;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_otpLoginModuleBloc.toAppearResendButtonCountown(second: _otpCountingTime);
});
super.initState();
}
#override
Widget build(BuildContext context) {
_otpLoginModuleBloc = OtpLoginModuleProivder.of(context);
return Container();
}
}
This should answer your questions. Thank you.
Oh and don't forget to add dispose method inside the OtpLoginModuleBloc that cancels Timer and closes both _buttonTimerController and _textTimerController in case you don't need the bloc anymore.
when I use Getx to update my Widget?
I do not know Rx() how to contact to the thing I put in.
code is _obx=Rx().
but I send data is "".obs. that is not Rx() but this is RxString().
when I use "".obs.value="newString". why Rx() can know that who updates data.
just like :
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetIncrementPage extends StatefulWidget {
GetIncrementPage({Key key}) : super(key: key);
#override
_GetIncrementPageState createState() => _GetIncrementPageState();
}
class _GetIncrementPageState extends State<GetIncrementPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('get'),
),
body: Container(
alignment: Alignment.center,
child: _body(),
),
);
}
Widget _body() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
OutlineButton(
child: Text('get 数字加减'),
onPressed: c.increment,
),
OutlineButton(
child: Text('get log 变化'),
onPressed: c.change,
),
Obx(() {
printInfo(info: '刷新了页面 get_example');
return Text(c.count.toString());
}),
ObxValue((v) {
printInfo(info: '刷新了页面 get_ObxValue_log1 ');
return Text('logValue:' + v.toString());
}, ObjectKey('key').obs),
Obx(() {
printInfo(info: '刷新了页面 get_obx_log1');
return Text('logObx:' + c.log.toString());
}),
Obx(() {
printInfo(info: '刷新了页面 get_obx_log2');
return Text(c.log2.toString());
}),
// ObxValue((var value) => Text('${value.toString()}'), c),
],
);
}
#override
void dispose() {
Get.delete<Controller2>();
super.dispose();
}
final Controller2 c = Get.put(Controller2());
}
///
/// Created by fgyong on 2020/10/22.
///
class Controller2 extends GetxController {
var count = 0.obs;
var count2 = 0.obs;
final log = ''.obs;
final log2 = ''.obs;
increment() => count++;
#override
void onClose() {
printInfo(info: 'Controller close');
super.onClose();
}
void change() {
log.value += ' ${log.value.length}';
}
}
when i change log.value to new String,why log2 do not fresh.
class Obx extends StatefulWidget {
final WidgetCallback builder;
const Obx(this.builder);
_ObxState createState() => _ObxState();
}
class _ObxState extends State<Obx> {
RxInterface _observer;
StreamSubscription subs;
_ObxState() {
_observer = Rx();
}
#override
void initState() {
subs = _observer.subject.stream.listen((data) => setState(() {}));
super.initState();
}
#override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final result = widget.builder();
if (!_observer.canUpdate) {
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
return result;
}
#override
Widget build(BuildContext context) => notifyChilds;
}
Why can rx() establish contact with the log, please help me. When I update
How can Rx() know when logging?
just help me.
You can use Obx or GetX widgets from Get to "listen" to changes to observable variables you declare in a GetxController.
I think you are also confusing Rx as an ObserVER vs. ObservABLE. Rx is an observable, i.e. you watch it for changes using Obx or GetX widgets, (I guess you can call these two widgets "Observers".)
Basic Example
class Log2Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
Controller c = Get.put(Controller());
// ↑ declare controller inside build method
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() => Text('${c.log2.value}')
),
RaisedButton(
child: Text('Add +1'),
onPressed: c.change,
)
],
),
),
),
);
}
}
class Controller extends GetxController {
RxInt log2 = 0.obs;
void change() => log2.value++;
}
You likely don't need a StatefulWidget when using GetX. A GetxController lives outside the lifecycle of widgets. State is stored in a GetX Controller (instead of in a StatefulWidget).
GetX takes care of streams & subscriptions through variables you declare as obs, like count.obs and log2.obs. When you want to "listen" or "observe", use Obx or GetX widgets. These automatically listen to obs changes of its child and rebuild when it changes.
Obx vs. GetBuilder vs. GetX
class Log2Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
Controller c = Get.put(Controller());
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() => Text('Obx: ${c.log2.value}')
),
// ↓ requires manual controller.update() call
GetBuilder<Controller>(
builder: (_c) => Text('GetBuilder: ${_c.log2.value}'),
),
// ↓ controller instantiated by Get widget
GetX<Controller>(
init: Controller(),
builder: (_c) => Text('GetX: ${_c.log2.value}'),
),
RaisedButton(
child: Text('Add +1'),
onPressed: c.change,
),
RaisedButton(
child: Text('Update GetBuilder'),
onPressed: c.update, // rebuild GetBuilder widget
),
],
),
),
),
);
}
}
class Controller extends GetxController {
RxInt log2 = 0.obs;
void change() => log2.value++;
}
Obx
Listens to observable (obs) changes. Controller needs to already be declared/initialized elsewhere to use.
GetX
Listens to observable (obs) changes. Can initialize controller itself using init: constructor argument, if not done elsewhere. Optional argument. Safe to use init: if Controller already instantiated. Will connect to existing instance.
GetBuilder
Does not listen to obs changes. Must be rebuilt manually by you, calling controller.update(). Similar to a setState() call. Can initialize controller itself using init: argument, if not done elsewhere. Optional.
First:
when I "".obx.value="newString".why Rx() can know.
This is wrong, the .obx doesn't exist, I guess you mean .obs;
When you create a OBS variable like: final a = ''.obs, the type of this var will be a RxString(), so you can use to observer this var whatever you want to.
I know two widgets can you use to observer in your screen:
GetX(), Obx()
see link https://github.com/jonataslaw/getx/issues/937,
when Obx() build,we named it ObxA, named "ABC".obs abcobs,
in Obx
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final result = widget.builder();
if (!_observer.canUpdate) {
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
return result;
}
when build,RxString() will execute get value,and addListen():
code is
set value(T val) {
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
subject.add(_value);
}
/// Returns the current [value]
T get value {
if (getObs != null) {
getObs.addListener(subject.stream);
}
return _value;
}
void addListener(Stream<T> rxGetx) {
if (_subscriptions.containsKey(rxGetx)) {
return;
}
_subscriptions[rxGetx] = rxGetx.listen((data) {
subject.add(data);
});
}
so They made a connection
I am into flutter to port my android app from java. One thing that is evident in flutter is widgets. Now my biggest obstacle to make my app work as it was on android is starting an async task to request data from the server. I have a custom progress dialog that can be shown or hidden.
class MySelection extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MySelectionState();
}
}
class MySelectionState extends State<MySelection> {
final globalKey = new GlobalKey<ScaffoldState>();
ProgressDialog progressDialog = ProgressDialog.getProgressDialog("Loading books ...");
List<Book> books;
void requestData() async {
EventObject eventObject = await getBooks();
books = eventObject.object;
populateData();
}
#override
Widget build(BuildContext context) {
if (books == null) {
books = List<Book>();
requestData();
}
var appBar = AppBar();
return Scaffold(
appBar: AppBar(
title: Text('Set up your Collection'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
books = List<Book>();
requestData();
},
),
],
),
body: SingleChildScrollView(
child: Stack(
Container(
height: (MediaQuery.of(context).size.height - (appBar.preferredSize.height * 2)),
padding: const EdgeInsets.symmetric(horizontal: 10),
margin: EdgeInsets.only(top: 50.0),
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: books.length,
itemBuilder: bookListView,
),
),
Container(
height: (MediaQuery.of(context).size.height),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: progressDialog,
),
],
),
),
}
}
Now, this code works well when I don't call the progress dialog unlike when I try to do that by calling my progressdialog widget.
if (books == null) {
progressDialog.showProgress();
books = List<Book>();
requestData();
}
It throws the error that
The method 'showProgress' was called on null. Receiver: null Tried
calling: showProgress()
Of course, the reason is that I am calling this before its widget is even created. Now my question is how can I do this because I can't afford to put a button for the user to click. I just want this to work on its own once the user is on this particular screen.
import 'package:flutter/scheduler.dart';
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
// add your code which you want to execute after your build is complete
});
}
Thanks.
Hello fellow developers,
For my first Flutter project I need to use a list with sticky headers and infinite scroll. I found a very nice library for this purpose.
https://pub.dev/packages/flutter_sticky_header
Final goal is to fetch new items into the list from my database by scrolling further down.
For testing purposes I added a button to add a random item to my list. However the UI is not updated when the function is called. I am very new to Flutter. Below my code. How can I update the UI every time an item is added to the list without recreating the widget.
class AppScaffold2 extends StatefulWidget {
#override
_AppScaffold2State createState() => _AppScaffold2State();
}
class _AppScaffold2State extends State<AppScaffold2> {
final CustomScrollView x = CustomScrollView(
slivers: new List<Widget>(),
reverse: false,
);
int counter = 0;
add(Widget w){
x.slivers.add(w);
}
#override
Widget build(BuildContext context) {
return DefaultStickyHeaderController(
child: Scaffold(
body: Container(child: Column(children: <Widget>[
Expanded(child: x),
MaterialButton(
onPressed: () => fetch(),
child: Text('add to list')
)
],),)
),
);
}
fetch() {
x.slivers.add(_StickyHeaderList(index: counter));
counter++;
}
}
class _StickyHeaderList extends StatelessWidget {
const _StickyHeaderList({
Key key,
this.index,
}) : super(key: key);
final int index;
#override
Widget build(BuildContext context) {
return SliverStickyHeader(
header: Header(index: index),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, i) => ListTile(
leading: CircleAvatar(
child: Text('$index'),
),
title: Image.network(
"https://i.ytimg.com/vi/ixkoVwKQaJg/hqdefault.jpg?sqp=-oaymwEZCNACELwBSFXyq4qpAwsIARUAAIhCGAFwAQ==&rs=AOn4CLDrYjizQef0rnqvBc0mZyU3k13yrg",
),
),
childCount: 6,
),
),
);
}
}
Try using setState() in fetch Method.
like this.
fetch() {
x.slivers.add(_StickyHeaderList(index: counter));
setState(() {
_counter++;
});
}```
Update state using setState.
fetch() {
x.slivers.add(_StickyHeaderList(index: counter));
setState(() {
_counter++;
});
}
In the code shown below , the dispatch event is called from within the build method after getting the BuildContext object. What if I wish to do is to dispatch an event during processing at the start of the page within the initState method itself ?
If I use didChangeDependencies method , then I am getting this error :
BlocProvider.of() called with a context that does not contain a Bloc of type FileManagerBloc. how to fix this?
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider<FileManagerBloc>(
builder: (context)=>FileManagerBloc(),
child: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(color: Colors.blueGrey, child: TopMenuBar()),
Expanded(
child: BlocBuilder<FileManagerBloc,FileManagerState>(
builder: (context , state){
return GridView.count(
scrollDirection: Axis.vertical,
physics: ScrollPhysics(),
crossAxisCount: 3,
crossAxisSpacing: 10,
children: getFilesListWidget(context , state),
);
},
),
)
],
),
),
),
));
}
#override
void dispose() {
super.dispose();
}
#override
void didChangeDependencies() {
logger.i('Did change dependency Called');
final FileManagerBloc bloc = BlocProvider.of<FileManagerBloc>(context) ;
Messenger.sendGetHomeDir()
.then((path) async {
final files = await Messenger.sendListDir(path);
bloc.dispatch(SetCurrentWorkingDir(path)) ;
bloc.dispatch(UpdateFileSystemCacheMapping(path , files)) ;
});
}
The problem is that you are initializing the instance of FileManagerBloc inside the BlocProvider which is, of course inaccessible to the parent widget. I know that helps with automatic cleanup of the Bloc but if you want to access it inside initState or didChangeDependencies then you have to initialize it at the parent level like so,
FileManagerBloc _fileManagerBloc;
#override
void initState() {
super.initState();
_fileManagerBloc= FileManagerBloc();
_fileManagerBloc.dispatch(LoadEducation());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider<FileManagerBloc>(
builder: (context)=> _fileManagerBloc,
child: SafeArea(
child: Container(
child: Column(
children: <Widget>[
Container(color: Colors.blueGrey, child: TopMenuBar()),
Expanded(
child: BlocBuilder<FileManagerBloc,FileManagerState>(
builder: (context , state){
return GridView.count(
scrollDirection: Axis.vertical,
physics: ScrollPhysics(),
crossAxisCount: 3,
crossAxisSpacing: 10,
children: getFilesListWidget(context , state),
);
},
),
)
],
),
),
),
));
}
#override
void dispose() {
_fileManagerBloc.dispose();
super.dispose();
}
#override
void didChangeDependencies() {
logger.i('Did change dependency Called');
Messenger.sendGetHomeDir()
.then((path) async {
final files = await Messenger.sendListDir(path);
_fileManagerBloc.dispatch(SetCurrentWorkingDir(path)) ;
_fileManagerBloc.dispatch(UpdateFileSystemCacheMapping(path , files)) ;
});
}
alternatively, if FileManagerBloc was provided/initialized at a grandparent Widget then it could easily be accessible at this level through BlocProvider.of<CounterBloc>(context);
you can use it in didChangeDependencies method rather than initState.
Example
#override
void didChangeDependencies() {
final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);
//do whatever you want with the bloc here.
super.didChangeDependencies();
}
Solution:
Step 1: need apply singleton pattern on Bloc class
class AuthBloc extends Bloc<AuthEvent, AuthState> {
static AuthBloc? _instance;
static AuthBloc get instance {
if (_instance == null) _instance = AuthBloc();
return _instance!;
}
....
....
Step 2: use AuthBloc.instance on main.dart for Provider
void main() async {
runApp(MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AuthBloc.instance,
),
...
...
],
child: App(),
));
}
Now you can use Bloc without context
you can get state by AuthBloc.instance.state from initState or anywhere
you can add event from anywhere by AuthBloc.instance.add(..)
you also call BlocA from another BlocB very simple