Rebuild stateful widget on update of other stateful widget in Flutter - asynchronous

I have two stateful widgets.
One I'll call the parent, because it's producing a list (of map markers) that is being passed to the second widget (the child, a map, in a different dart file).
I have a default list of markers that is updated in an async function in the parent widget.
Despite using setState() to update the list, the child widget (map) is unaffected by this update.
Is there any way I can await before passing data to a widget in the build method?
class ParentWidget extends StatefulWidget{
.............
}
class _ParentWidgetState extends State<ParentWidget> {
List markers = [LatLng(0,0)];
#override
void initState() {
super.initState();
updateMarkers();
}
updateMarkers () async {
updatedMarkers = await getUpdatedMarkers()
setState((){
markers = updatedMarkers
});
# # print(updatedMarkers)
# # ^^ prints the correct data that I want to pass in MyMap
}
#override
Widget build(BuildContext context) {
return Scaffold(
............
body: MyMap(markers: markers);
# # The original markers ([LatLng(0,0)]) is the only data getting passed to MyMap
# # How can I get the updatedMarkers passed to the MyMap widget???????
}

I think this video might be your answer: https://www.youtube.com/watch?v=d_m5csmrf7I
He has two StatefulWidgets (Slider and Chart). By using a Provider, he changesthe Chart data when the slider moves.

Related

Flutter State Management Examples

In a complex app, sometimes a Global Variable 'attached' to a widget, can be changed by some 'EXTERNAL EVENT' such as (1) A timer that run in another thread, or (2) socket.io server emit event (3) Others ......
Let's call this global variable gintCount and the app has 3 pages, namely:
Page 1: A 'Dynamic' page that need to display the latest value of gintCount.
Page 2: Another 'Dynamic' page that need to display the latest value of gintCount, with a Text Input Field.
Page 3: A 'Static' page that do nothing when gintCount changes.
Suppose the user is doing something in Page 1 or Page 2, when and where should we 'Refresh' the page to display the latest value that may/might be changed by EXTERNAL event?
I read the other Q&A in Stack Overflow and it is said that there are 4 ways for the State Management of Flutter, they are namely:
Using setState
Using ScopedModal
Using Rxdart with BLoC
Using Redux
Since I'm a newbie in Flutter, I am completely lost in 2 to 4, so I've build an app using no. 1, i.e. setState. to demonstrate how we can manage states in flutter. And I hope, in the future, I am able to (or somebody else) provide answers by using no. 2 to 4.
Let's take a look at the running app in the following animation gif:
Screen Shot Gif Link
As you can see in the gif, there is a Global Counter in Page 1 and Page 2, and Page 3 is a static Page.
Let me explain how I did it:
The complete source code can be found at the following address:
https://github.com/lhcdims/statemanagement01
There are 7 dart files, they are namely:
gv.dart: Stores all the Global Variables.
ScreenVariable.dart: Get the height/width/font size of screen etc. You may ignore this.
BottomBar.dart: The bottom navigation bar.
main.dart: The main program.
Page1.dart: Page 1 widget.
Page2.dart: Page 2 widget.
Page3.dart: Page 3 widget.
Let's first take a look at gv.dart:
import 'package:flutter/material.dart';
class gv {
static var gstrCurPage = 'page1'; // gstrCurPage stores the Current Page to be loaded
static var gintBottomIndex = 0; // Which Tab is selected in the Bottom Navigator Bar
static var gintCount = 0; // The Global Counter
static var gintCountLast = 0; // Check whether Global Counter has been changed
static var gintPage1Counter = 0; // No. of initState called in Page 1
static var gintPage2Counter = 0; // No. of initState called in Page 2
static var gintPage3Counter = 0; // No. of initState called in Page 3
static bool gbolNavigatorBeingPushed = false; // Since Navigator.push will called the initState TWICE, this variable make sure the initState only be called once effectively!
static var gctlPage2Text = TextEditingController(); // Controller for the text field in Page 2
}
How did I simulate an External Event that changes the global variable gv.gintCount?
Ok, I create a thread in main.dart that runs the timer 'funTimerExternal', and increment gv.gintCount every second!
Now, let's take a look at main.dart:
// This example tries to demonstrate how to maintain the state of widgets when
// variables are changed by External Event
// e.g. by a timer of another thread, or by socket.io
// This example uses setState and a timer to maintain States of Multiple Pages
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import "package:threading/threading.dart";
import 'gv.dart';
import 'Page1.dart';
import 'Page2.dart';
import 'Page3.dart';
import 'ScreenVariables.dart';
void main() { // Main Program
var threadExternal = new Thread(funTimerExternal); // Create a new thread to simulate an External Event that changes a global variable defined in gv.dart
threadExternal.start();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
sv.Init(); // Init Screen Variables
runApp(new MyApp()); // Run MainApp
});
}
void funTimerExternal() async { // The following function simulates an External Event e.g. a global variable is changed by socket.io and see how all widgets react with this global variable
while (true) {
await Thread.sleep(1000);
gv.gintCount += 1;
}
}
class MyApp extends StatefulWidget { // Main App
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
initState() {
super.initState();
var threadTimerDefault = new Thread(funTimerDefault); // *** Set funTimerDefault, to listen to change of Vars ***
threadTimerDefault.start();
}
void funTimerDefault() async {
while (true) {
await Thread.sleep(500); // Allow this thread to run each XXX milliseconds
if (gv.gintCount != gv.gintCountLast) { // Check any changes need to setState here, if anything changes, setState according to gv.gstrCurPage
gv.gintCountLast = gv.gintCount;
switch (gv.gstrCurPage) {
case 'page1':
setState(() {}); // Page 1: Refresh Page
break;
case 'page2':
setState(() {}); // Page 2: Refresh Page
break;
default: // Page 3: Do Nothing, since Page 3 is static
break;
}
}
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // Disable Show Debug
home: MainBody(),
);
}
}
class MainBody extends StatefulWidget {
#override
_MainBodyState createState() => _MainBodyState();
}
class _MainBodyState extends State<MainBody> {
#override
initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
switch (gv.gstrCurPage) { // Here Return Page According to gv.gstrCurPage
case 'page1':
return ClsPage1();
break;
case 'page2':
return ClsPage2();
break;
default:
return ClsPage3();
break;
}
return ClsPage1(); // The following code will never be run, to avoid warning only
}
}
As you can see, I use another timer 'funTimerDefault' to keep track of changes in gv.gintCount, and determine whether setState should be called every XXX milliseconds. (XXX is currently set at 500)
I know, this is stupid!
How can I create similar examples by using ScopedModal, or Rxdart with BLoC, or Redux?
Before anyone provides any answers, please bear in mind that the Global Variable gintCount, is not changed by ANY USER INTERACTION, but an EXTERNAL EVENT that IS NOT PART OF ANY WIDGETS. For example, you can regard this app as:
A CHAT app, that 'gintCount' is a message sent to you by someone else thru socket.io server. Or,
A Multi-Player On-line Game, that 'gintCount' is the position of another player in YOUR SCREEN, which is controlled by that player using another Mobile Phone!
For your need, you should definitely look more into the architectures available, that you talked about.
For example, REDUX matches exactly what you need to solve your issue.
I can only advise you to take a look at this presentation of REDUX :
https://www.youtube.com/watch?v=zKXz3pUkw9A
It is very understandable even for newbies of this pattern (which I was not so long ago).
When you've done that, take a look at http://fluttersamples.com/
This website contains example projects for a dozen of different patterns. That may help you get started
I've rewritten the example using Redux, let's take a look at the screen cap:
As you can see, there are 2 counters in Page 1, the variables are stored in gv.dart
In gv.dart (The dart file that stores all Global Variables), I created a 'Store':
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'dart:convert';
enum Actions { Increment } // The reducer, which takes the previous count and increments it in response to an Increment action.
int counterReducer(int intSomeInteger, dynamic action) {
if (action == Actions.Increment) {
// print('Store Incremented: ' + (intSomeInteger + 1).toString());
return intSomeInteger + 1;
}
return intSomeInteger;
}
class gv {
static Store<int> storeState = new Store<int>(counterReducer, initialState: 0);
static var gstrCurPage = 'page1'; // gstrCurPage stores the Current Page to be loaded
static var gintBottomIndex = 0; // Which Tab is selected in the Bottom Navigator Bar
static var gintGlobal1 = 0; // Global Counter 1
static var gintGlobal2 = 0; // Global Counter 2
static var gintPage1Counter = 0; // No. of initState called in Page 1
static var gintPage2Counter = 0; // No. of initState called in Page 2
static var gintPage3Counter = 0; // No. of initState called in Page 3
static bool gbolNavigatorBeingPushed = false; // Since Navigator.push will called the initState TWICE, this variable make sure the initState only be called once effectively!
static var gctlPage2Text = TextEditingController(); // Controller for the text field in Page 2
}
Again, in main.dart, I created another thread 'funTimerExternal' to simulate an 'External Event' that some global variables are changed by, say, socket.io server emit event.
At the end of 'funTimerExternal', after some variables are changed, I called:
gv.storeState.dispatch(Actions.Increment);
to change the state of Page1 OR Page2, IF AND ONLY IF the user is navigating Page 1 or Page 2. (i.e. do nothing when user is navigating Page 3)
main.dart :
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:threading/threading.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'gv.dart';
import 'Page1.dart';
import 'Page2.dart';
import 'Page3.dart';
import 'ScreenVariables.dart';
void main() { // Main Program
var threadExternal = new Thread(
funTimerExternal); // Create a new thread to simulate an External Event that changes a global variable defined in gv.dart
threadExternal.start();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
sv.Init(); // Init Screen Variables
runApp(new MyApp()); // Run MainApp
});
}
void funTimerExternal() async { // The following function simulates an External Event e.g. a global variable is changed by socket.io and see how all widgets react with this global variable
while (true) {
await Thread.sleep(1000);
gv.gintGlobal1 += 1;
gv.gintGlobal2 = (gv.gintGlobal1 / 2).toInt();
gv.storeState.dispatch(Actions.Increment);
}
}
class MyApp extends StatefulWidget { // Main App
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return StoreProvider(
store: gv.storeState,
child: MaterialApp(
debugShowCheckedModeBanner: false, // Disable Show Debug
home: MainBody(),
),
);
}
}
class MainBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
switch (gv.gstrCurPage) {
// Here Return Page According to gv.gstrCurPage
case 'page1':
gv.gintPage1Counter += 1;
return StoreConnector<int, int>(
builder: (BuildContext context, int intTemp) {
return new ClsPage1(intTemp);
}, converter: (Store<int> sintTemp) {
return sintTemp.state;
},);
break;
case 'page2':
gv.gintPage2Counter += 1;
return StoreConnector<int, int>(
builder: (BuildContext context, int intTemp) {
return new ClsPage2(intTemp);
}, converter: (Store<int> sintTemp) {
return sintTemp.state;
},);
break;
default:
return ClsPage3();
break;
}
}
}
Unlike the example provided on the web, the 'Store' is not declared inside main.dart, but inside another dart file gv.dart. i.e. I separated the UI and data!
The complete example can be found here:
https://github.com/lhcdims/statemanagement02
Thanks again for the help of Miiite and shadowsheep.

Customize jfoenix slider indicator

For the jfoenix slider, the indicator always changed with the slider and show the value accordingly. How can we customize the value in indicate, for example when slide the value from 0-4 we want to show "Small" on the indicate.
I've digged out the answer by myself, just post it here in case someone else need that appreciate it if you can let me know some better ones. This one is for formatting the value in the indicator to one decimal point and of course you can add some logic to show some string based on the value.
yourSlider.setValueFactory(new Callback<JFXSlider, StringBinding>() {
#Override
public StringBinding call(JFXSlider arg0) {
return Bindings.createStringBinding(new java.util.concurrent.Callable<String>(){
#Override
public String call() throws Exception {
DecimalFormat df = new DecimalFormat("#.0");
return df.format(yourSlider.getValue());
}
}, yourSlider.valueProperty());
}
});

javafx CheckBoxTreeItem update parents programmatically

I have to retrieve some data from my database to dynamically create a TreeView and select some CheckBoxTreeItems from this TreeView. This TreeView represents permissions to a menu structure.
My doubt is when I create the TreeView and select specific items from the Tree according to the user's permissions programmatically, the parents items don't have any status change (selected or indeterminate). But when I select any item directly from the interface, the parents get updated.
For example, here I have my screen when I select the items programmatically:
You can see that I have two menu items selected, but the parents aren't.
On this image, I have selected the same menu items using the screen, and the parents were updated with indeterminate status or selected if I select all children inside the submenu.
I have gone through the documentation, google and here on Stack Overflow, but only found examples to update the children.
Is there a way to update the parents programmatically or to call the event executed from the screen when an item is selected?
EDIT:
All items from the Tree have the independent property set to false.
I came with a workaround for this problem.
I had to first create all the TreeView structure, and change the selected property after using this code snippet:
Platform.runLater(new Runnable() {
#Override
public void run() {
selectItems();
}
});
Here is the code to verify the TreeItems:
private void selectItems(){
TreeItem root = tree.getRoot();
if (root != null) {
selectChildren(root);
}
}
private void selectChildren(TreeItem<TesteVO> root){
for(TreeItem<TesteVO> child: root.getChildren()){
// HERE I CHECK IF THE USER HAS PERMISSION FOR THE MENU ITEM
// IF SO, I CHANGE THE SELECTED PROPERTY TO TRUE
if (child.getValue().id == 4) {
((CheckBoxTreeItem) child).setSelected(true);
}
// IF THERE ARE CHILD NODES, KEEP DIGGING RECURSIVELY
if(!child.getChildren().isEmpty()) {
selectChildren(child);
}
}
}
If there is a simpler way, please let me know!
This is not the case. Parent items do get automatically get set to the indeterminate state when you select a child item. I'm not sure if this is something that got corrected from the time that this question was posted, probably not.
My guess is that there's a programming bug in how the node was selected or how the TableView was constructed and initialized.
Here's some code that shows what I'm doing, and it works! In my case, I'm using a CheckBoxTreeItem<File> for the TreeItem.
How the treeview was created
treeView = new TreeView(root);
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object o, Object t1) {
CheckBoxTreeItem<File> node = (CheckBoxTreeItem<File>)t1;
if (node.getValue() != currentFile) {
setFileDetail(node);
showChildren(node);
}
}
});
treeView.setCellFactory(new CallBackWrapper());
treeView.setShowRoot(false);
Below show the CallBackWrapper class.
private class CallBackWrapper implements Callback<TreeView<File>, TreeCell<File>> {
Callback<TreeView<File>, TreeCell<File>> theCallback;
private CallBackWrapper() {
theCallback = CheckBoxTreeCell.<File>forTreeView(getSelectedProperty, converter);
}
#Override
public TreeCell<File> call(TreeView<File> fileTreeView) {
return theCallback.call(fileTreeView);
}
final Callback<TreeItem<File>, ObservableValue<Boolean>> getSelectedProperty = (TreeItem<File> item) -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>) item).selectedProperty();
}
return null;
};
final StringConverter<TreeItem<File>> converter = new StringConverter<TreeItem<File>>() {
#Override
public String toString(TreeItem<File> object) {
File item = object.getValue();
return fileSystemView.getSystemDisplayName(item);
}
#Override
public TreeItem<File> fromString(String string) {
return new TreeItem<File>(new File(string));
}
};
}
And lastly here some code that the selection was made in:
boolean selectNode(CheckBoxTreeItem<File> parentNode, String name) {
Object[] children = parentNode.getChildren().toArray();
for (Object child : children) {
CheckBoxTreeItem<File> childItem = (CheckBoxTreeItem<File>) child;
if (name.equals(childItem.getValue().getName())) {
childItem.setSelected(true);
//treeView.getSelectionModel().select(child); <-- this does not work!
return true;
}
}
return false;
}

Layout of Layer in GlassPane not working as expected

I installed a Layer in the GlassPane like that:
MobileApplication.getInstance().getGlassPane().getLayers().add(myLayer);
Now I would expect the content of myLayer to be alligned in the center of the glassPane like it is when I add a layer to View:
view.getLayers().add(myLayer)
Although alignment is set to center I get the following result:
I noticed the layoutBounds of the layer added to glassPane being all "0", while the layoutBounds of the layer in view are identical to the view layoutBounds.
Furthermore I don't need to call layer.show() as stated in the gluon documentation ("Showing a Layer is then achieved either by calling show(), ..."), because the layer is immediately shown after it is added to a layer.
Am I missing something?
I suggest you have a look at the detailed documentation here, and not only to the JavaDoc.
There you will find a more detailed explanation on how layers can be added and created.
The preferred way is by using addLayerFactory(), like:
#Override
public void init() {
addViewFactory(BASIC_VIEW, () -> new BasicView(BASIC_VIEW));
addLayerFactory("My Layer", () -> new SidePopupView(new StackPane(new Button("Side"))));
}
The layer will be hidden unless you show it with MobileApplication.getInstance().showLayer("My Layer").
You can create your own Layer implementation, like:
private class MyLayer extends Layer {
private final Node root;
private final double size = 150;
public MyLayer() {
root = new StackPane(new Button("A custom layer"));
root.setStyle("-fx-background-color: white;");
getChildren().add(root);
getGlassPane().getLayers().add(this);
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(size, size);
resizeRelocate((getGlassPane().getWidth() - size)/2, (getGlassPane().getHeight()- size)/2, size, size);
}
}
and add it as well to the factory:
addLayerFactory("My Layer", () -> new MyLayer());
But notice you will have to resize and relocate it, otherwise you will get 0,0 location as in your picture, and take care of its visibility.
Or you can use built-in layers, like SidePopupView, and you won't need to worry about these more low level details.

How to get all selected tree items in a tree view in JavaFX

I need to be able to get an updated list of all selected items in a tree view (which has multiple selection on).
This example: Tree item select event in javafx2
shows how to respond/identify one selected item at a time. Is there a way to get all selected items at once? Something like the hypothetical non-working code below:
ArrayList<TreeItem> selectedTreeItems = new ArrayList<>();
myTreeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
selectedTreeItems.clear();//reset the list. correct?
//get a new list of children of the root
ObservableList objects = myTreeView.getRoot().getChildren();
//loop to get the selected items.
for (int i = 0; i < objects.size(); i++) {
TreeItem object = (TreeItem) objects.get(i);
if (thisObjectIsSelected(object)) {
selectedTreeItems.add(object);
}
}
}
});
privatevoid thisObjectIsSelected(TreeItem item){
//what do I do here?
}
I am not sure how to achieve what I want. Any help is greatly appreciated!
Just observe and refer to the selection model's getSelectedItems() list:
myTreeView.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<TreeItem>() {
#Override
public void onChanged(Change<? extends TreeItem> change) {
// myTreeView.getSelectionModel().getSelectedItems() contains all the selected items
}
});

Resources