Making Value Reactive with GetX in Widget Setup - flutter

I'm new to GetX and am trying to migrate a project from Provider. I am using multi_split_view in my app to have resizable panes, and I set it up in a StatefulWidget like this:
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<PilotProApp> {
final MultiSplitViewController _splitViewController = MultiSplitViewController();
#override
Widget build(BuildContext context) {
//var menu = Provider.of<ModelMenu>(context); //<-- Old provider variable
final menu = Get.put(ControllerMenu()).menu; //<-- New GetX observable
//Update splitView areas for Dashboard
_splitViewController.areas = menu.value == 'Dashboard'
? [Area(weight: .75), Area(weight: .25)]
: [Area(weight: .25), Area(weight: .75)];
MultiSplitView splitView = MultiSplitView(
controller: _splitViewController,
children: const [
Master(),
Detail(),
],
);
MultiSplitViewTheme splitViewTheme = MultiSplitViewTheme(
data: MultiSplitViewThemeData(dividerThickness: 16),
child: splitView,
);
}
}
I know that I have to wrap a widget in Obx(()=>) in order to make it reactive, but the place I need reactivity here is where it says //Update splitView areas for Dashboard. In Provider, when the menu variable changed, it would reset the _splitViewController.areas.
But with GetX, I'm unclear on how to make a configuration change like that reactive. Where should Obx(()=>) go in this case? Or do I need to move my entire MultiSplitViewController into my ControllerMenu somehow?

Well that didn't take long to figure out. Let me know if I'm wrong on this, but I think I can make my widget a StatelessWidget and put my MultiSplitViewController in my controller like this:
class ControllerMenu extends GetxController {
final menu = 'Dashboard'.obs;
final splitViewController = MultiSplitViewController();
#override
void onInit() {
super.onInit();
//Watch for changes on `menu`
ever(
menu,
(value) {
//Update controller
if (value == 'Dashboard') {
splitViewController.areas = [Area(weight: .75), Area(weight: .25)];
} else {
splitViewController.areas = [Area(weight: .25), Area(weight: .75)];
}
},
);
}
}
Then I just plug in my MultiSplitViewController back into my view:
Widget build(BuildContext context) {
final splitViewController = Get.put(ControllerMenu()).splitViewController;
MultiSplitView splitView = MultiSplitView(
controller: splitViewController,
children: const [
Master(),
Detail(),
],
);
}
Seems to work great!

Related

Create / Manage dynamic TextEditingControllers

I got a ListView.builder that generates n number of elements and I am looking at adding a controller for each of them. I have seen some approaches of adding a controller to a list of controllers and then access them by the index however I am just wondering how will this impact the performance of the screen if lets say you have 20 controllers? Are there some best practices for this scenario? Should you even go down this line or avoid it?
I suggest to introduce a Widget for all items in list.
Make sure you dispose in a correct place for the performance.
Also I request to store the user entered value with the object of item will help to restore on scrolls.
Eg:
class YourWidget extends StatefulWidget {
const YourWidget({Key? key, required this.item}) : super(key: key);
final YourItem item;
#override
State<YourWidget> createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
final controller = TextEditingController();
#override
void initState() {
super.initState();
controller.text = widget.item.enteredValue;
}
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: (value){
widget.item.enteredValue = value;
},
...
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
class YourItem {
String? id;
...
String enteredValue = '';
}

How to use GetX for data passing in Flutter?

My first Flutter development with GetX. Now encounter a problem.
I have a ListView where the items are all encapsulated Class.
The requirement now is to create an obs List as a data source. The elements in the List are all models.
I now want to pass the model in the List to the item, and click on the item to pass it to the next page for data modification. what should I do?
I am like this
`
Controller:
class FindQADetailController extends GetxController {
var detailEntity = QADetailEntity().obs;
}
Page:
class FindQAPage extends StatefulWidget {
const FindQAPage({Key key}) : super(key: key);
#override
State<FindQAPage> createState() => _FindQAPageState();
}
class _FindQAPageState extends BasePageMixin<FindQAPage, FindQAPresenter>
with SingleTickerProviderStateMixin
implements FindQAIView {
final findQAController = Get.put(FindQAController());
#override
void initState() {
super.initState();
_refresh();
}
#override
Widget build(BuildContext context) {
return RezaAppContainer(
childWidget: Obx(() => DeerListView(
itemCount: findQAController.listData.length,
onRefresh: _refresh,
loadMore: _loadMore,
hasMore: findQAController.hasMore,
itemBuilder: (_, index) {
var entity = findQAController.listData[index];
return FindItemQAPage(entity);
})),
);
}
Item:
class FindItemQAPage extends StatefulWidget {
FindItemQAPage(this.entity, {Key key}) : super(key: key);
QAEntity entity;
#override
State<FindItemQAPage> createState() => _FindItemQAPageState();
}
class _FindItemQAPageState
extends BasePageMixin<FindItemQAPage, FindItemQAPresenter>
with SingleTickerProviderStateMixin
implements FindItemQAIView {
FindItemQAController findItemQAController = Get.put(FindItemQAController());
#override
void initState() {
super.initState();
findItemQAController.entity.value = widget.entity;
}
}
`
I want the elements in the array in the first page to be passed to the item and the next page, and the data modifications made on the next page to be passed to the item in the first page.
Passing Data from 1st Screen to 2nd Screen
You can pass any data using arguments parameters in navigation methods
Get.to(ScreenName(),arguments: PASS_DATA);
If you are doing navigation with routes still you can pass data
Get.toNamed(RoutesName, arguments: PASS_DATA);
For navigate data back from 2nd screen to 1st screen you can user result property.
Get.back(result:PASS_DATA);
Pass data from 1st screen to 2nd screen & vice versa.
1st screen controller
import 'package:get/get.dart';
class FirstController extends GetxController {
/// Navigation method.
Future<void> btnNavigateTap() async {
//.... Using Get.toNamed method || use this if you are using routeName
//.... Pass Data from 1st screen to 2nd screen
Get.toNamed(
SeconScreenRouteName,
arguments: {
"user": "Jems",
"emails": ["abc#gmail.com", "pqr#gmail.com"],
},
);
//.... Using Get.to method
//.... Pass Data from 1st screen to 2nd screen
Get.to(
SecondScreen(),
arguments = {
"user": "Jems",
"emails": ["abc#gmail.com", "pqr#gmail.com"],
},
);
//.... Get Data from 2nd screen to 1st screen
final result = await Get.to(
SecondScreen(),
arguments = {
"user": "Jems",
"emails": ["abc#gmail.com", "pqr#gmail.com"],
},
);
}
}
2nd screen controller to access data.
import 'package:get/get.dart';
class SecondController extends GetxController {
late String user;
late List<String> emails;
dynamic argumentData;
#override
void onInit() {
super.onInit();
user = argumentData['user'];
emails = argumentData['emails'];
}
void btnBackTap(){
//... Passing data to previous screen.
Get.back(result:{
"user":"Jack",
});
}
}
For more details about navigation & parsing data using getX check this reference link

Update TextEditingController Text with Riverpod

I'm new to Riverpod and am trying to migrate an app over from Provider. If I had a TextField and wanted to set its value based on my Provider model, I would do this:
class MyWidget extends StatefulWidget{
const MyWidget({ Key? key }) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var controller = TextEditingController();
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Set the value here...
var model = Provider.of<Model>(context);
controller.text = model.name;
}
Widget build(BuildContext context) {
return TextField(controller: controller)
}
}
As I understand it, didChangeDependencies() would listen to changes from Provider.of<Model>(context) and update my controller accordingly.
I'm trying to pull off the same thing with Provider, but I can't ever get the TextField's value to show up.
class MyWidget extends ConsumerStatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
ConsumerState<ConsumerStatefulWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends ConsumerState<MyWidget> {
var controller = TextEditingController();
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Trying the same thing here...
final name = ref.watch(providerName);
controller.text = name;
}
Widget build(BuildContext context) {
final name = ref.watch(providerName);
return Column(
children: [
//This doesn't work:
TextField(controller: controller),
//I know my provider has the value, because this works fine:
Text(name),
]
}
}
How can I get my TextEditingController's text property to update?
From Riverpod official website
///1.Create a [StateNotifier] sub-class, StateNotifier is something where you can define functions that can change your state like in this state is of String type, you also can use objects (Classes instead of primitive types)
class Counter extends StateNotifier<String> {
Counter() : super('');
void changeText(String text){
state=text;
}
///2.Create a provider [StateNotifierProvider] with this you can use in your widget
final counterProvider = StateNotifierProvider<Counter, String>((ref) {
return Counter();
});
///3.Consume the Provider this is how we can attach state with our widget
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final text = ref.watch(counterProvider);
return Text('$text');
}
}
so here you can add you widget like button and onTap executes the code like
onTap()=>changeText(textController.text);
So your text [Text('$text');] will automatically change.
String inputText = controller.text;

Riverpod : Alternate way of overriding initState inside ConsumerWidget

What is the solution of initializing things inside consumerWidget as because the initState method is not overridable here?
Riverpod v2.1.3
You can use ConsumerStatefulWidget and ConsumerState
final helloWorldProvider = Provider((_) => 'Hello world');
class RiverpodExample extends ConsumerStatefulWidget {
const RiverpodExample({super.key});
#override
ConsumerState<RiverpodExample> createState() => _RiverpodExampleState();
}
class _RiverpodExampleState extends ConsumerState<RiverpodExample> {
#override
void initState() {
super.initState();
final value = ref.read(helloWorldProvider);
print(value); // Hello world
}
#override
Widget build(BuildContext context) {
final value = ref.watch(helloWorldProvider);
return Text(value); // Hello world
}
}
I'm not totally sure how to answer your question as I have not worked with ConsumerWidget. I'd presume the idea is to keep most of your state in providers.
However, I would like to recommend using hooks_riverpod alongside flutter_hooks (same developer).
This makes keeping state local to the widget simple and also provides easy access to providers.
For example:
class Example extends HookWidget {
const Example({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final test = useProvider(Test.provider());
final controller = useTextEditingController();
final loading = useState(false);
final buttonText = useState('Change me!');
return Column(
children: [
TextField(controller: controller),
if (!loading) RaisedButton(
onPressed: () async {
loading.value = true;
await Future.delayed(const Duration(seconds: 1));
buttonText.value = controller.text;
loading.value = false;
}
child: Text(buttonText.value),
),
if (loading) const CircularProgressIndicator(),
// Do something with providers, etc.
],
),
);
}
Just a quick example, but there are plenty of resources (flutter_hooks, hooks_riverpod) to help you along. Also, check out examples from the developer on riverpod hooks usage.
I may be late but with the upcoming 1.0.0 of Riverpod you will be able to use https://pub.dev/documentation/flutter_riverpod/1.0.0-dev.2/flutter_riverpod/ConsumerStatefulWidget-class.html and https://pub.dev/documentation/flutter_riverpod/1.0.0-dev.2/flutter_riverpod/ConsumerState-class.html, which does exactly what you want.

Passing data to StatefulWidget and accessing it in it's state in Flutter

I have 2 screens in my Flutter app: a list of records and a screen for creating and editing records.
If I pass an object to the second screen that means I am going to edit this and if I pass null it means that I am creating a new item. The editing screen is a Stateful widget and I am not sure how to use this approach https://flutter.io/cookbook/navigation/passing-data/ for my case.
class RecordPage extends StatefulWidget {
final Record recordObject;
RecordPage({Key key, #required this.recordObject}) : super(key: key);
#override
_RecordPageState createState() => new _RecordPageState();
}
class _RecordPageState extends State<RecordPage> {
#override
Widget build(BuildContext context) {
//.....
}
}
How can I access recordObject inside _RecordPageState?
To use recordObject in _RecordPageState, you have to just write widget.objectname like below
class _RecordPageState extends State<RecordPage> {
#override
Widget build(BuildContext context) {
.....
widget.recordObject
.....
}
}
Full Example
You don't need to pass parameters to State using it's constructor.
You can easily access these using widget.myField.
class MyRecord extends StatefulWidget {
final String recordName;
const MyRecord(this.recordName);
#override
MyRecordState createState() => MyRecordState();
}
class MyRecordState extends State<MyRecord> {
#override
Widget build(BuildContext context) {
return Text(widget.recordName); // Here you direct access using widget
}
}
Pass your data when you Navigate screen :
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MyRecord("WonderWorld")));
class RecordPage extends StatefulWidget {
final Record recordObject;
RecordPage({Key key, #required this.recordObject}) : super(key: key);
#override
_RecordPageState createState() => new _RecordPageState(recordObject);
}
class _RecordPageState extends State<RecordPage> {
Record recordObject
_RecordPageState(this. recordObject); //constructor
#override
Widget build(BuildContext context) {. //closure has access
//.....
}
}
example as below:
class nhaphangle extends StatefulWidget {
final String username;
final List<String> dshangle;// = ["1","2"];
const nhaphangle({ Key key, #required this.username,#required this.dshangle }) : super(key: key);
#override
_nhaphangleState createState() => _nhaphangleState();
}
class _nhaphangleState extends State<nhaphangle> {
TextEditingController mspController = TextEditingController();
TextEditingController soluongController = TextEditingController();
final scrollDirection = Axis.vertical;
DateTime Ngaysx = DateTime.now();
ScrollController _scrollController = new ScrollController();
ApiService _apiService;
List<String> titles = [];
#override
void initState() {
super.initState();
_apiService = ApiService();
titles = widget.dshangle; //here var is call and set to
}
I have to Navigate back to any one of the screens in the list pages but when I did that my onTap function stops working and navigation stops.
class MyBar extends StatefulWidget {
MyBar({this.pageNumber});
final pageNumber;
static const String id = 'mybar_screen';
#override
_MyBarState createState() => _MyBarState();
}
class _MyBarState extends State<MyBar> {
final List pages = [
NotificationScreen(),
AppointmentScreen(),
RequestBloodScreen(),
ProfileScreen(),
];
#override
Widget build(BuildContext context) {
var _selectedItemIndex = widget.pageNumber;
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
elevation: 0,
backgroundColor: Colors.white,
unselectedItemColor: Colors.grey.shade700,
selectedItemColor: Color(kAppColor),
selectedIconTheme: IconThemeData(color: Color(kAppColor)),
currentIndex: _selectedItemIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() {
_selectedItemIndex = index;
});
},
You should use a Pub/Sub mechanism.
I prefer to use Rx in many situations and languages. For Dart/Flutter this is the package: https://pub.dev/packages/rxdart
For example, you can use a BehaviorSubject to emit data from widget A, pass the stream to widget B which listens for changes and applies them inside the setState.
Widget A:
// initialize subject and put it into the Widget B
BehaviorSubject<LiveOutput> subject = BehaviorSubject();
late WidgetB widgetB = WidgetB(deviceOutput: subject);
// when you have to emit new data
subject.add(deviceOutput);
Widget B:
// add stream at class level
class WidgetB extends StatefulWidget {
final ValueStream<LiveOutput> deviceOutput;
const WidgetB({Key? key, required this.deviceOutput}) : super(key: key);
#override
State<WidgetB> createState() => _WidgetBState();
}
// listen for changes
#override
void initState() {
super.initState();
widget.deviceOutput.listen((event) {
print("new live output");
setState(() {
// do whatever you want
});
});
}
In my app, often instead of using stateful widgets, I use mainly ChangeNotifierProvider<T> in main.dart, some model class
class FooModel extends ChangeNotifier {
var _foo = false;
void changeFooState() {
_foo = true;
notifyListeners();
}
bool getFoo () => _foo;
}
and
var foo = context.read<FooModel>();
# or
var foo = context.watch<FooModel>();
in my stateless widgets. IMO this gives me more precise control over the rebuilding upon runtime state change, compared to stateful widgets.
The recipe can be found in the official docs, the concept is called "lifting state up".