Getx Controller not fetching data - flutter

So i have an implementation where i pass the id of a parent category and it fetches its child category from the database and displays them in another screen (view).
When i click on the parent, it sends the its category ID to the screen whose code is below and the it suppose to get the data from the database to be used in the screen. However, it says null when you click from the previous page to this new screen. If you directly do a hot reload, the data gets displayed.
Does it mean the below code doesn't get run when the screen is first loaded?
Note that all controllers and repositories have been set in the dependecies class and initialised in the main.
var childCatItem =
Get.find<ChildCategoriesController>().getChildCategory(childCatId);
Parent Category Screen Function to Pass ID.
The below code gets the ID from the already loaded data.
onTapLeftService: (){
int childCatId = randomController.isLoaded ? randomController.randomServicesList[0].children[0].id : 0;
Get.toNamed(RouteHelper.getChildCategoryServicesPage(childCatId));
},
The below code is the route where it is gotten and passed to the screen
//This is the route implementation. This is called in the above code.
static String getChildCategoryServicesPage(int childCatId) =>
'$childCategoryServicesPage?childCatId=$childCatId';
GetPage(
name: childCategoryServicesPage,
page: () {
var childCatId = Get.parameters['childCatId'];
return ChildCategoryServicesScreen(
childCatId: int.parse(childCatId!));
},
transition: Transition.fadeIn),
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:personal_start/controller/nonemergency_services/child_categories_controller.dart';
import 'package:personal_start/helper/app_styles.dart';
import 'package:personal_start/widget/title_text.dart';
class ChildCategoryServicesScreen extends StatelessWidget {
int childCatId; //Here I receive the ID of the parent category into this screen
ChildCategoryServicesScreen({Key? key, required this.childCatId})
: super(key: key);
#override
Widget build(BuildContext context) {
var childCatInstance = Get.find<ChildCategoriesController>(); // I create an instance of the controller
var childCatItem =
Get.find<ChildCategoriesController>().getChildCategory(childCatId); //Here I make an API call to the database, passing the parent category ID.
print(childCatItem);
print(childCatInstance.childCategory!.title); //I receive a null value here.
return Scaffold(
appBar: AppBar(
title: TitleTextWidget(titleText: 'Child Cat Title'),
leading: GestureDetector(
onTap: () {
Get.back();
},
child: const Icon(Icons.arrow_back_outlined),
),
backgroundColor: AppStyles.appPrimaryColor,
),
// body: !childCatItem.isLoaded
// ? const CustomLoader()
// : Center(child: TitleTextWidget(titleText: 'It has Loaded')),
);
}
}

you can pass data using getx argument
https://www.kindacode.com/article/using-getx-get-for-navigation-and-routing-in-flutter/

Try this way
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:personal_start/controller/nonemergency_services/child_categories_controller.dart';
import 'package:personal_start/helper/app_styles.dart';
import 'package:personal_start/widget/title_text.dart';
class ChildCategoryServicesScreen extends GetView<ChildCategoriesController> {
ChildCategoryServicesScreen({Key? key}): super(key: key);
#override
ChildCategoriesController controller = Get.put(ChildCategoriesController());
#override
Widget build(BuildContext context) {
...//TODO: YOUR CODE HERE
}
ChildCategoriesController
class ChildCategoriesController extends GetxController {
late int childCatId;
...//TODO: YOUR CODE HERE
#override
void onInit() {
int childCatId = Get.arguments[0];
...//TODO: YOUR CODE HERE
super.onInit();
}
#override
void onClose() {}
...//TODO: YOUR CODE HERE
}

Related

Read nested widget/class properties value in flutter

I'm building a simple app with lots of nested widgets/classes from different specialised files
list of files:
main.dart -> the menu file used to start the activity
"Activity()"
group_widgets.dart -> the file that contains the custom widget
"CustomWidget()"
file_a.dart -> the file that uses the custom widgets
inside the "Activity()"
other.dart -> other files that needs to manage data changed in CustomWidget()
inside main.dart:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Activity(),
));
},
inside group_widgets.dart:
class CustomWidget extends StatefulWidget {
const CustomWidget({Key? key}) : super(key: key);
#override
State<CustomWidget> createState() => _CustomWidgetState();
}
class _CustomWidgetState extends State<CustomWidget> {
var _boolean = false;
bool switchBoolean(bool state) => !state;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => {
setState(() {
_boolean = switchBoolean(_boolean);
})
},
child: Container(
color: _boolean == true ? Colors.green : Colors.red,
),
);
}
}
inside file_a.dart
class Activity extends StatefulWidget {
const Activity({Key? key}) : super(key: key);
#override
State<Activity> createState() => _ActivityState();
}
class _ActivityState extends State<Activity> {
#override
Widget build(BuildContext context) {
bool boolean = true;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CustomWidget(),
Text('Here where to show the variable from CustomWidget'
'and prove I can retrieve it')
],
),
),
);
}
}
inside other.dart
if ( booleanFromCustomWidget == true) {
Something ...
}
What is the best practice to achieve it?
I've read a lot here but nothing seems to well fit my needing.
Just comment if my request is not as clear as it seems to me))
Please correct me if I am wrong, but if you want to access data from parent widgets from inside their descendants (children or even nested children) you can either pass them down via parameter arguments:
Child(int age, String name);
And then accept it in the new file, where the Child widget lives, via its constructor:
class Child {
String name;
int age;
// Constructor
Child(String passedName, int passedAge) {
this.name = passedName;
this.age = passedAge;
}
}
Inside the parent.dart you then have to import the children.dart to use it.
Or use a popular package like the provider package: https://pub.dev/packages/provider
This allows you to store data containers, which you can access basically anywhere in your code. Feel free to google it & watch some tutorials to get started, as it is the preferred approach to avoid passing data to widget which really do not care about the passed parameters.
Note: You can transfer the idea to output the String data like in your example code above.
you can use a state manager like provider, or bloc
At the top level, you set up the data services

Flutter bloc is not rebuilding in 7.2.0 version with Equatable

I created simple app to test bloc 7.2.0 and faced that BlocBuilder doesn't rebuild after first successful rebuild. On every other trigger bloc emits new state, but BlocBuilder ignores it.
Please note, if I remove extends Equatable and its override from both, state and event, then BlocBuilder rebuilds UI every time Button pressed. Flutter version 2.5.1
If Equatable is necessary, why it's not working with it? If Equatable isn't necessary, why it's been used in initial creation via VSCode extension.
My code:
bloc part
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
//bloc
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial()) {
on<MainButtonPressedEvent>(_onMainButtonPressedEvent);
}
void _onMainButtonPressedEvent(
MainButtonPressedEvent event, Emitter<MainState> emit) {
emit(MainCalculatedState(event.inputText));
}
}
//states
abstract class MainState extends Equatable {
const MainState();
#override
List<Object> get props => [];
}
class MainInitial extends MainState {}
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
}
//events
abstract class MainEvent extends Equatable {
const MainEvent();
#override
List<Object> get props => [];
}
class MainButtonPressedEvent extends MainEvent {
final String inputText;
const MainButtonPressedEvent(this.inputText);
}
UI part
import 'package:bloc_test/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: BlocProvider(
create: (context) => MainBloc(),
child: SubWidget(),
),
),
);
}
}
class SubWidget extends StatelessWidget {
TextEditingController inputText = TextEditingController();
String? exportText;
#override
Widget build(BuildContext context) {
MainBloc mainBloc = BlocProvider.of<MainBloc>(context);
return BlocBuilder<MainBloc, MainState>(
builder: (context, state) {
if (state is MainCalculatedState) {
exportText = state.exportText;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${exportText ?? ''} data'),
SizedBox(
width: 200,
child: TextField(
controller: inputText,
),
),
ElevatedButton(
onPressed: () =>
mainBloc.add(MainButtonPressedEvent(inputText.text)),
child: const Text('Button')),
],
),
);
},
);
}
}
Equatable is used to make it easy for you to program, how and when states are the same (no update) and when they are different (update).
Your updates do not work because you are sending the same state repeatedly, but you did not tell the Equatable extension how to find out if they are different. So they are all the same.
So to make sure your program understands that some states of the same kind are indeed different and should cause an update, you need to make sure you mention what makes them different:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
// this tells the Equatable base class to consider your text property
// when trying to figure out if two states are different.
// If the text is the same, the states are the same, so no update
// If the text is different, the states are different, so it will update
#override
List<Object> get props => [this.exportText];
}
If you remove Equatable altogether, two newly instanciated states are never equal, so that would solve your problem as well... except that at some point you will want them to be, and then you need to add it back in.
Your MainCalculatedState needs to override the props getter from Equatable and return the list of all properties which should be used to assess equality. In your case it should return [exportText].
Example:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
#override
List<Object> get props => [exportText];
}

Flutter GetX controller not calling method when revisit the app screen

I am new to the Flutter GetX package and facing problem when using the flutter GetX package. I have several app screens where one screen for listing all the products from the database.
In my product controller, I am fetching all the data from the database and showing with listview like as given code below and It's working fine.
Problem: When I'm inserting a new record from another controller and come back to the product list controller it'll not showing newly added data. Actually this time onInit method won't fire.
Controller code
class ProductIndexCtrl extends GetxController {
var products = [].obs;
#override
void onInit() {
super.onInit();
getAll();
}
void getAll() {
Product.getAll().then((jsonList) {
products.value = Product.fromJsonList(jsonList);
});
}
}
class ProductCreateCtrl extends GetxController {
void saveData(Product product) {
...
...
//after successful insert
Get.toNamed('productIndex');
}
}
Product index screen
final ctrl = Get.put(ProductIndexCtrl());
GetX<ProductIndexCtrl>(builder: (controller) {
return _listview(controller.products);
}
Widget _listview(data) {
...
...
}
As the GetX dependency manager controls the lifecycle of dependencies, you should not manually call them. It's GetX's responsibility when to call them.
Therefore you need to manually call getAll() method of your ProductIndexCtrl controller inside the saveData() method of your ProductCreateCtrl like:
saveData(Product product){
....
....
final indexCtrl= Get.find<ProductIndexCtrl>();
indexCtrl.getAll();
}
By returning to that page, you can return the new information locally to the previous page
> Controller code
class ProductIndexCtrl extends GetxController {
var products = [].obs;
#override
void onInit() {
super.onInit();
getAll();
}
void getAll() {
Product.getAll().then((jsonList) {
products.value = Product.fromJsonList(jsonList);
});
}
}
> Product index screen
class ProductCreateCtrl extends GetxController {
void saveData(Product product) {
...
...
//after successful insert
Get.back(result: product);
}
}
and get Data when back
Get.toName('ProductCreateCtrl').then(result){
products.add(result);
}
I tried a similar thing with one single controller. The code snippet is given below.
First, create the ProductView. Since this is the entry point of the application, So you will create a GetX controller inside of this.
/// THIS IS PARENT VIEW SO WE WILL CREATE GETX CONTROLLER HERE
class ProductView extends StatelessWidget {
const ProductView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller = Get.put(ProductController());
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: Obx(() {
return controller.myProductList.isEmpty
? showNoProductView()
: ListView.builder(
itemCount: controller.myProductList.length,
itemBuilder: (context, index) {
return YourListItemView(controller.myProductList[index]);
},
);
}),
);
}
}
The view AddProductView is responsible for adding new products to the DB. We can assume that there is a FloatingActionButton present in ProductView and onClick on that button, we will open this AddProductView.
/// THIS IS CHILD VIEW SO WE WILL FIND THE PRODUCT CONTROLLER HERE
class AddProductView extends StatelessWidget {
const AddProductView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller = Get.find<ProductController>();
return Scaffold(
appBar: AppBar(
title: const Text('Add Product'),
),
body: Column(
children: [
// ADD YOUR OTHER WIDGETS TO GET PRODUCT INFO
TextButton(
child: const Text('Click to Add'),
onPressed: () {
var productName = nameTextEditingController.text;
var productQuantity = qtyTextEditingController.text;
var product = YourProductObject(productName, productQuantity);
controller.addProduct(product: product);
},
)
],
),
);
}
}
Finally, the controller will look like this.
import 'package:get/get.dart';
class ProductController extends GetxController {
// this will be your custom product list object
var myProductList = <YourProductObject>[].obs;
var dbInstance = YourDbInstance();
#override
void onReady() async {
super.onReady();
// perform database operation
await fetchDataFromDb();
}
Future<void> fetchDataFromDb() async {
// assuming that data is coming as List<YourProductListObject>
// always use try catch in db operation. for demo purpose I am skipping that.
var productListFromDb = await dbInstance.getYourProductListObjectList();
myProductList.assignAll(productListFromDb);
}
Future<void> addProduct({required YourProductObject product}) async {
// assuming that there is a function that returns true if a product is added to db
var isAdded = await dbInstance.addProduct(product);
if (isAdded) {
myProductList.add(product);
}
}
}
Since myProductList is a RxList so getx will observe it and will update the UI accordingly. You must add Obx((){}) in view.

How to force stateful widget redraw using keys?

I'm making an app that pulls data from an API and displays it in a view (MVC style).
I need to figure out how to force my view widget to redraw itself. Right now I tried with ValueKeys and ObjectKeys but to no avail.
There's lots and lots of code so I am going to use snippets as much as possible to keep it clear. (If you need to see more code feel free to ask)
Here's my view widget:
class view extends StatefulWidget{
view({
Key key,
this.count = 0,
}) : super(key: key);
int count;
String _action='';
var _actionParams='';
var _data;
Function(String) callback;
void setAction(String newAction){
_action = newAction;
}
void setActionParams(String params){
_actionParams = jsonDecode(params);
}
void setData(String data){
_data = jsonDecode(data);
}
void incrementCounter(){
count++;
}
#override
_viewState createState() => _viewState();
}
class _viewState extends State<view>{
Object redrawObject = Object();
#override
Widget build(BuildContext context) {
/*
switch(widget._action){
case '':
break;
default:
return null;
}
*/
return Text("Counter: "+widget.count.toString());
}
#override
void initState(){
this.redrawObject = widget.key;
super.initState();
}
}
You can see in the commented code that I am planning to change the way the view builds itself in function of the data that gets passed to it.
What I have tried so far is to pass a ValueKey/ObjectKey to the view from main.dart in a constructor and then changing the object at runtime. Unfortunately that did not work.
At the top of my main.dart(accessible from anywhere within main) I have this:
Object redraw = Object();
final dataView = new view(key: ObjectKey(redraw));
Then in the body of the homepage I have the view and a floating button right under.
If I press the button it should increment the counter inside the view and force it to redraw. Here's the code I have tried so far:
body: Center(
child: dataView
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.badge),
onPressed: (){
dataView.incrementCounter();
redraw = new Object();
},
),
From what I understand, if the object that was used as a key gets changed, then flutter should rebuild the state for the widget. So I'm setting my object to a new object but it's not working.
I also tried something like this:
onPressed: (){
setState((){
dataView.incrementCounter();
redraw = new Object();
});
},
Eventually I'd like to use a navigator in conjunction with my view widget (so that we have a back button) but I don't know if this is possible.
It feels a bit like I'm fighting with the framework. Is there a different paradigm I should use (like pages?) or is it possible for me to do it this way?
How do I force my view widget to get redrawn?
Using Göktuğ Vatandaş' answer and GlobalKeys I was able to figure it out.
I made a reDraw() function inside the state and then I called it from my main using a GlobalKey.
Note: Wrapping in a container and using a key for the container is not necessary. Calling setState() is enough to force a redraw.
Here's the new view widget:
import 'dart:convert';
import 'package:flutter/cupertino.dart';
GlobalKey<_viewState> viewKey = GlobalKey();
class view extends StatefulWidget{
view({
Key key,
this.count = 0,
}) : super(key: key);
int count;
String _action='';
var _actionParams='';
var _data;
Function(String) callback;
void setAction(String newAction){
_action = newAction;
}
void setActionParams(String params){
_actionParams = jsonDecode(params);
}
void setData(String data){
_data = jsonDecode(data);
}
void incrementCounter(){
count++;
}
#override
_viewState createState() => _viewState();
}
class _viewState extends State<view>{
#override
Widget build(BuildContext context) {
/*
switch(widget._action){
case '':
break;
default:
return null;
}
*/
return Text("Counter: "+widget.count.toString());
}
#override
void initState(){
super.initState();
}
void reDraw(){
setState((){});
}
}
Here's where I declare the view widget in my main:
final dataView = new view(key: viewKey);
Here's where I call the reDraw() function:
floatingActionButton: FloatingActionButton(
child: Icon(Icons.badge),
onPressed: (){
dataView.incrementCounter();
viewKey.currentState.reDraw();
},
),
Thanks Göktuğ Vatandaş!
You can check flutter_phoenix's logic for redraw effect. I think its very useful or you can just use package itself. Basically it does what you trying to achive.
It creates a unique key in state.
Key _key = UniqueKey();
Injects it to a container.
#override
Widget build(BuildContext context) {
return Container(
key: _key,
child: widget.child,
);
}
And when you call rebirth it just refresh key and that causes view to rebuild.
void restartApp() {
setState(() {
_key = UniqueKey();
});
}

Flutter pass data to new screen with onTap

My application has a bottom navigation bar, with 2 pages in the menu.
On page 1, I can fill out a form and it calculates me values ​​that it displays to me by pushing in a 1.1 page.
On this page I have a button that allows me to redirect me to page 2 as if I clicked menu 2 of the navigation bar.
This works. My problem is how to send the data from my page 1.1 to this page 2.
The goal being that my page 2 is a form which is empty if I call it by the navigation bar but which is filled automatically if I pass by the page 1.1 in focus of the calculated values.
Here an exemple of the redirection that I do:
Here is my code :
my_app.dart :
final ThemeData _AppTheme = AppTheme().data;
final navBarGlobalKey = GlobalKey(); // => This is my key for redirect page
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp>{
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'App',
home: MyBottomNavigationBar(),
theme: _AppTheme,
navigatorKey: locator<NavigationService>().navigatorKey,
onGenerateRoute: Router.generateRoute,
initialRoute: HOME_ROUTE,
);
}
}
My bottom_navigation_bar.dart :
class MyBottomNavigationBar extends StatefulWidget
{
MyBottomNavigationBar({Key key}) : super(key: key);
#override
_MyBottomNavigationBarState createState() => _MyBottomNavigationBarState();
}
class _MyBottomNavigationBarState extends State<MyBottomNavigationBar>
{
int _pageIndex = 0;
final List<Widget> _pagesOption = [
page1.1(), // => Here I load direclty my page 1.1 with data for the exemple
page2(),
];
void onTappedBar(int index)
{
setState(() {
_pageIndex = index;
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child : Scaffold(
body : _pagesOption.elementAt(_pageIndex),
bottomNavigationBar: BottomNavigationBar(
key: navBarGlobalKey,
currentIndex: _pageIndex,
onTap: onTappedBar,
type: BottomNavigationBarType.fixed,
items : [
BottomNavigationBarItem(
icon : Icon(Icons.home),
title : Text('Home')
),
BottomNavigationBarItem(
icon : Icon(Icons.settings),
title : Text('Setting')
),
]
),
)
);
}
}
And here my widget submit button of page 1.1 :
Widget _getSubmitButton(){
return RaisedButton(
child: Text(
'Send'
),
onPressed: () {
final BottomNavigationBar navigationBar = navBarGlobalKey.currentWidget;
navigationBar.onTap(1); // => How to send data that I have in my page ???
},
);
}
For this, you can use Shared Preferences, the main idea is that:
Store the value of the calculated value in SharedPref from Page 1 when you're passing to Page 1.1
Let you checks for the value by default in Page 2's initState(), any changes in the Shared Preferences will be fetched in the Page 2 itself, using SharedPref get method.
WHY?
This is probably a cleaner way to achieve what you want, since in the BottomNavigationBar will not help you do this, but a Shared Preferences value will always give you that data which you can use it any time
Let's see how you can achieve this:
PAGE ONE
// Set the data of the form here
class _PageOneState extends State<PageOne>
{
void onSubmit() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
//make sure you store the calculated value, if that is String
// use setString() or if it is an int, setInt()
// and then pass it to the SharedPref
// key is a string name, which is used to access
// key and store, so choose the name wisely
await prefs.setInt("key", your_calculated_value);
}
}
PAGE TWO
class _PageTwoState extends State<PageTwo>
{
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
// This will be responsible for getting the result from SharedPref
int calculated_value;
#override
void initState(){
super.initState();
// get your list here
calculated_value = _prefs.then((SharedPreferences prefs){
// here if no data is then _values will have 0
// which you can use it to check and populate data
return (prefs.getInt("key") ?? 0);
});
}
}
This is the most reasonable way of doing the thing which you want. In this manner, whenever, PageTwo will trace any values, it will reflect, else, your choice for 0 check result. Let me know, if you have any doubts in that.
In your FirstActivity
onPressed: () {
navigatePush(SecondActivity(text: "Data"));
}
In your SecondActivity
class SecondActivity extends StatefulWidget {
String text;
SecondActivity({this.text});
}
You can pass the the values as arguments when you push to your new screen. This could get messy if you're building a larger project.
A cleaner implementation would be to use a Provider. Set up the data you want in a model mixed in with ChangeNotifier and use Provider.of<*name of your class*>(context) where ever you need to use it.