Flutter- Passing Data between Custom Widgets inside column - flutter

I'm stuck with a WidgetA() which calculates a String but I'd like to pass this string to WidgetB() - how do I pass the updated data to WidgetB ?
return Column(
children: [
WidgetA() // calculates value
WidgetB() // needs to use this value
],
);
Widget A is a costum Class Stateful Widget

The most obvious way, to me, to do this is to store the data in the state of the parent widget. You could then provide WidgetA with a callback to set this state, and provide WidgetB with the value:
int value;
setValue(int newValue) {
setState(() {
value = newValue;
});
}
return Column(
children: [
WidgetA(callback: (int newValue) => this.setValue(newValue)) // calculates value
WidgetB(value: this.value) // needs to use this value
],
);
WidgetA and WidgetB could then look something like the following:
class WidgetA extends StatelessWidget {
final Function callback;
// get the callback as a named argument
WidgetA({required this.callback});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
print('The button was pressed!');
// get the value and call the callback with it
callback(42);
},
child: Text('Press me!'));
}
}
class WidgetB extends StatelessWidget {
final int value;
WidgetB({required this.value});
#override
Widget build(BuildContext context) {
return Text('The number is $value');
}
}
Depending on the value and how often it's updated, using some sort of storage like shared preferences for the value is also possible.

Create a shared memory, for example, a class, and save the value to a class variable.
class SharedMemory {
String stringCalculatedByWidgetA;
}
Then make sure that WidgetB updates the value in the shared memory.

Related

Is it possible to share and update one screen's reactive value in another screen without Provider?

So I have this block of code in a widget that navigates to another screen:
screen_one.dart
class ScreenOne extends StatefulWidget {
const ScreenOne({ super.key });
#override
State<ScreenOne> createState() => _ScreenOneState();
}
class _ScreenOneState extends State<ScreenOne> {
List<String> state = [''];
#override
Widget build(BuildContext context) {
return Column(
MaterialButton(
onPressed: () => Navigator.pushNamed(context, '/screen-two'),
child: Text('Click here.')
),
Text(state[0]),
);
}
}
screen_two.dart
class ScreenTwo extends StatelessWidget {
const ScreenTwo({ super.key });
#override
Widget build(BuildContext context) {
return Container();
}
}
Basically I need to pass the state variable from ScreenOne to ScreenTwo and then update it there (in ScreenTwo)
ScreenTwo needs to display the same thing as ScreenOne and add() a new item to the state list when some button is clicked which should show on both the screens.
Its just one simple List so I am trying to avoid using provider.
Is it possible to do though?
I'm currently just passing it through the Navigator:
screen_one.dart
Navigator.pushNamed(
context,
'/post-info',
arguments: state,
),
screen_two.dart
Widget build(BuildContext context) {
final List<String> post = ModalRoute.of(context)!.settings.arguments as List<String>;
// ...
}
first I want to recommend you when things go bigger and more complex, it's better to use a state management approach, However since you did say that you have only one List you can simply use a ValueNotifier, with ValueListenableBuilder:
// this should be outside widget classes, maybe in a custom-made class or just in a global scope.
ValueNotifier stateNotifier = ValueNotifier([""]);
now in the places you want to use that state, you can use ValueListenableWidget like this:
ValueListenableBuilder(
valueListenable: stateNotifier,
builder: (context, value, child) {
return Column(
children: [
Text('${state[0]}'),
MaterialButton(
onPressed: () {
Navigator.pushNamed(context, '/screen-two'),
},
child: Text('click'),
),
],
);
},
);
}
}
and any other place where you want to see that state get updates, you need to use ValueListenableWidget.
Now, for executing a method like add() on the List and notify the widgets, you need to assign a new value for it like this:
void addInTheList(String elem) {
List current = stateNotifier.value;
current.add(elem);
// this exactly what will be responsible for updating.
stateNotifier.value = current;
}
now, you can just call addInTheList and expect it to update in all of them:
addInTheList("Example");

Flutter: Is it possible to know if you're currently off stage?

I have a number of pages in my app wrapped in Offstage widgets. Each page makes use of the provider package to render based on state updates (e.g. the user does something, we make a network call and display the result).
As the pages are wrapped in Offstage widgets, the build() methods (and subsequent network calls) are called even if it's not the current page.
Is there a way inside the build() method to know if the widget is currently off stage (and if so, skip any expensive logic)?
I'm assuming I can work something with global state etc, but I was wondering if there was anything built-in in relation to the Offstage widget itself, similar to mounted
You can try finding the parent OffStage widget and see if the offstage property is true or false
#override
Widget build(BuildContext context) {
final offstageParent = context.findAncestorWidgetOfExactType<Offstage>();
if (offstageParent != null && offstageParent.offstage == false) {
// widget is currently offstage.
print('offstaged child');
} else {
// widget is not offstage
print('non-offstaged child');
}
return const Text('Example Widget');
}
I made a custom-made mechanism for the goal you wanna achieve:
First, I am declaring a new Map<String, bool> in a separate file alone that will hold the offStage bool value with the key of each class widget.
Map<String, bool> offStageMap = {};
then in the implementation of the StatefulWidget where the offstage widget is in:
class ExampleWidget extends StatefulWidget {
ExampleWidget({super.key}) {
widgetMapKey = runtimeType.toString();
}
late final String widgetMapKey;
#override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
final bool defaultIsOffStaged = false;
bool? localStateIsOffStages;
#override
void initState() {
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
setState(() {
localStateIsOffStages =
offStageMap[widget.widgetMapKey] = !previousIsOffStaged;
});
},
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
} },
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
}
let me explain what this is about.
first I declared a defaultIsOffStaged where it should be the initial offStage value when nothing is saved in that map.
when that widget is inserted in the widget tree (initState() called), the widget.widgetMapKey of the ExampleWidget widget will be saved in that map with the value of the default one which is defaultIsOffStaged.
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
in the offstage property o the OffStage widget, in this line:
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
the nullable localStateIsOffStages will be null for the first time since it has no value yet, so offStageMap[widget.widgetMapKey]! which equals to defaultIsOffStaged will be the bool value of offstage.
until now what we have, is a map containing the key that belongs only to the ExampleWidget which is its widget.widgetMapKey with its offStage value, right?
now from all places in your app, you can get the offStage value of that widget with its widgetMapKey like this:
print(offStageMap[ExampleWidget().widgetMapKey]); // true
now let's say you want to change the offstage property of that widget, in my code I used a simple example of GestureDetector, so when we tap in the Text("toggle offstage") area, it toggles offStage, here is what happens:
we got the existing value in the map:
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
then assign the opposite of it, to that widget key in the map, and the localStateIsOffStages bool variable which was nullable, now it has a value.
and as normal so the state updates I wrapped it in a SetState(() {})
now the widget's offstage will be toggled, and every time the widget key in the map will be updated with that new value.
the localStateIsOffStages I declared just to hold the local state when this is happening while the StatefulWidget state updates.
after the StatefulWidget is disposed of (when you pop the route as an example) and open that route again, the initState() will execute but since we have now an entry in the map, it's not null so nothing will happen inside initState().
the localStateIsOffStages will be null, so the offStage property of the Offstage widget will be the value from the map, which is the previous value before the widget is disposed.
that's it, from other places you can check for the offstage value of that specific widget like this:
print(offStageMap[ExampleWidget().widgetMapKey])
you can do it for all your widget pages, so you will have a map containing the offStage values of them all.
I take it one step up, and made those methods that I guess they will help:
this will return a List with the pages where the value is true.
List<String> offstagedPages() {
List<String> isOffStagedPages = [];
offStageMap.forEach((runtimeType, isOffStaged) {
if (isOffStaged) {
isOffStagedPages.add(runtimeType);
}
});
return isOffStagedPages;
}
this will return a true if a page is off staged and false if not:
bool isPageWidgetOffStaged(String runtimeType) {
if (offStageMap.containsKey(runtimeType)) {
return offStageMap[runtimeType]!;
}
return false;
}
Hope this helps a little.
Maybe it's not applicable to you, but you might be able to solve it by simply not using Offstage. Consider this app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool showFirst = true;
void switchPage() {
setState(() {
showFirst = !showFirst;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
Offstage(offstage: !showFirst,child: A("first", switchPage)),
Offstage(offstage: showFirst,child: A("second", switchPage)),
]))));
}
}
class A extends StatelessWidget {
final String t;
final Function onTap;
const A(this.t, this.onTap, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('$t is building');
return TextButton(onPressed: ()=> onTap(), child: Text(t));
}
}
You will notice by the prints that both pages are build. But if you rewrite it like this without Offstage, only the visible one is build:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
if (showFirst) A("first", switchPage),
if (!showFirst) A("second", switchPage),
]))));
}
If you want to just keep state alive your pages , you can use https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html , you may check this blog for example usage, https://medium.com/manabie/flutter-simple-cheatsheet-4370a68f98b3
If you are using Navigator, you can just extends NavigatorObserver. Then you will get didpush and didpop, use state to manage elementlifecycle, you will get page onPause and onResume fun.

Widget not updating flutter

I'm trying to change the variable from another stateful class.
class first extends statefulwidget {
bool text = false;
Widget build(BuildContext context) {
setState((){});
return Container(
child: text ? Text('Hello') : Text('check')
);
}
}
class second extends statefulwidget {
Widget build(BuildContext context) {
return Container(
child: IconButton(
onPressed: () {
first fir = first();
setState((){
fir.test = true;
});
}
)
);
}
}
widget shows only check not showing Hello
This is my code...Ignore spelling mistakes and camelcase
Give me the solutions if you know..
If you are trying to access data on multiple screens, the Provider package could help you. It stores global data accessible from all classes, without the need of creating constructors. It's good for big apps.
Here are some steps to use it (there is also a lot of info online):
Import provider in pubspec.yaml
Create your provider.dart file. For example:
class HeroInfo with ChangeNotifier{
String _hero = 'Ironman'
get hero {
return _hero;
}
set hero (String heroName) {
_hero = heroName;
notifyListeners();
}
}
Wrap your MaterialApp (probably on main.dart) with ChangeNotifierProvider.
return ChangeNotifierProvider(
builder: (context) => HeroInfo(),
child: MaterialApp(...),
);
Use it on your application! Call the provider inside any build method and get data:
#override
Widget build(BuildContext context){
final heroProvider = Provider.of<HeroInfo>(context);
return Column {
children: [
Text(heroProvider.hero)
]
}
}
Or set data:
heroProvider.hero = 'Superman';
try to reference to this answer, create function to set boolean in class1 and pass as parameter to class 2 and execute it :
typedef void MyCallback(int foo);
class MyClass {
void doSomething(int i){
}
MyOtherClass myOtherClass = new MyOtherClass(doSomething);
}
class MyOtherClass {
final MyCallback callback;
MyOtherClass(this.callback);
}

Flutter-getx, widget does not re-render when RxMap set to a new value

I'm building a widget that renders based on the contents of an RxMap. I start with the RxMap initialized to empty, and upon clicking a button set the RxMap to contain a new value. After setting the new value for the RxMap, the widget does not re-render to display the new map values.
Here's my code:
(EDIT: included all the boilerplate as well to prevent confusion. Each class is in a separate file with relevant imports in all the other files.)
class MyController extends GetxController {
var selectedItem = {}.obs;
}
class MyBinding extends Bindings {
#override
void dependencies() {
Get.put(MyController());
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext highContext) {
return GetMaterialApp(
title: 'App',
getPages: [
GetPage(
name: "/test",
page: () => MyClass(),
binding: MyBinding()),
],
initialRoute: "/test",
);
}
}
final saController = Get.find<MyController>();
class MyClass extends StatelessWidget {
Widget build(context) {
print(saController.selectedItem);
return Scaffold(
child: Column(
children: [
Obx(() => Container(
child: Text(saController.selectedItem.toString())
)),
TextButton(
child: Text("click"),
onPressed: (() {
saController.selectedItem = RxMap({"test": "item"});
saController.selectedItem.refresh();
saController.update();
print(saController.selectedItem);
})
)
]
)
);
}
}
The first print statement correctly prints the empty object to the console and the onPressed print statement correctly prints the new object with its key and value included to the console, but the widget does not re-render with the new value. refresh() and update() don't appear to do anything.
This seems to be specific to RxMap, I've been able to get other datatypes to update on change successfully, e.g. swapping out the RxMap value for a boolean value and toggling it from true to false on click causes the widget to re-render correctly without changing any other parts of the code.
Your UI will not trigger an update unless the value of a Rx variable (selectedItem.value) is changed. But you are updating the Rx variable (selectedItem) itself.
Therefore,you need to change saController.selectedItem = RxMap({"test": "item"}); to saController.selectedItem.value = {"test": "item"};
And I think you don't actually need to call refresh() and update() anymore.

How to Retrieve value from DropdownFormField?

I used dropdownformfield to get the gender of user in the sign up page, I created the widget in other class, I wanna know how can I control the field or retrieve the value when it changes because it doesnt have a controller unlike textformfield.
I would suggest adding an onChanged callback that can be passed into the constructor of the class containing the dropdown field.
class DropdownField extends StatelessWidget {
final Function(dynamic) onChanged;
DropdownField({#required this.onChanged});
#override
Widget build(BuildContext context) {
return DropdownButtonFormField(
onChanged: this.onChanged,
);
}
}
Then when you instantiate the class, have it manipulate something on callback.
class _OtherClassState extends State<OtherClass> {
var _selectedItem;
#override
Widget build(BuildContext context) {
return DropdownField(
onChanged: (newItem) => setState(() => this._selectedItem = newItem),
);
}
}