I have a getx controller and a method to update data in the database, I just wonder that how can I refresh of update the UI constantly after fetching the API? Here is my controller
class AdditionalContactController extends GetxController {
var additionalContactList = <AdditionalContact>[].obs;
var isLoading = true.obs;
UserController _userController = Get.find();
#override
void onInit() {
super.onInit();
_userController.getMail().then((value) async {
await _userController.getUser(value);
getAdditionalContacts(_userController.user.value.id);
});
}
//Update contact
Future<bool> updateContact({...}) async {
var response = await http.post(
Uri.parse(
"https://..."),
body: {
...
});
var jsonData = jsonDecode(response.body);
if (jsonData == "failed") {
return false;
}
return true;
}
}
you can use the ever worker to call a method that executes every time an Rx have assigned with a new value:
class AdditionalContactController extends GetxController {
var additionalContactList = <AdditionalContact>[].obs;
#override
void onInit() {
super.onInit();
ever(additionalContactList, methodToExecute)
});
}
methodToExecute(list) {
/* Code that will execute every time the additionalContactList changes */
}
now everytime additionalContactList is changed like as example if we assign a new List to it:
additionalContactList.value = [];
Then the methodToExecute() will be executed automatically, and will do every single time.
Related
So I have a Stateful Widget which has a List variable I want to update from the API call. My issue is that the List is empty even after I do the fetchItems() in the initState().
How can I then update the itemsList with the content of the fetchItems function?
Isnt the function suppose to update itemsList if I use setState().
class _ItemsWidgetState extends State<ItemsWidget> {
List<ItemsModel> itemsList = [];
void initState(){
fetchItems();
}
fetchItems() async {
final response = await http.get(url);
if (response.statusCode == 200) {
final fetchedItems = json.decode(response.body);
for (var item in fetchedItems) {
ItemsModel item = ItemsModel.fromJson(item);
setState(() {
itemsList.add(item);
});
}
} else {
throw Exception('Failed to load items');
}
}
Avoid calling setState inside loops, call it after your task has done.
Always call super.initState and mark initState as overrided
class _ItemsWidgetState extends State<ItemsWidget> {
List<ItemsModel> itemsList = [];
#override
void initState(){
super.initState();
fetchItems();
}
fetchItems() async {
final response = await http.get(url);
if (response.statusCode == 200) {
final fetchedItems = json.decode(response.body);
for (var item in fetchedItems) {
ItemsModel item = ItemsModel.fromJson(item);
/// Remove from setState
itemsList.add(item);
}
/// Tells to Flutter that now something has changed
setState(() {});
} else {
throw Exception('Failed to load items');
}
}
First check fetchedItems is a list type.
class _ItemsWidgetState extends State<ItemsWidget> {
List<ItemsModel> itemsList = [];
void initState(){
fetchItems();
}
fetchItems() async {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final fetchedItems = jsonDecode(response.body);
for (var item in fetchedItems) {
ItemsModel item = ItemsModel.fromJson(item);
setState(() {
itemsList.add(item);
});
}
} else {
throw Exception('Failed to load items');
}
}
I have a page with this code:
class _HomeScreenState extends State<HomeScreen> {
bool isFirstLoading = true;
#override
void initState() {
super.initState();
if (isFirstLoading) {
getInfo();
setState(() {
isFirstLoading = false;
});
} else {
getInfoFromSharedPref();
}
}
Future<http.Response> getInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
Loader.show(context,
isAppbarOverlay: true,
isBottomBarOverlay: true,
progressIndicator: CircularProgressIndicator());
var url = kLinkAPI + "/getInfo";
var response =
await http.post(url, headers: {"Content-Type": "application/json"});
var resObj = jsonDecode(response.body);
if (response != null) {
setState(() {
if (resObj.length > 0) {
address = resObj[0]['address'];
countryInfo = resObj[0]['country_info'];
phone = resObj[0]['phone'];
latitude = resObj[0]['latitude'];
longitude = resObj[0]['longitude'];
isFirstLoading = false;
prefs.setString('address', address);
prefs.setString('countryInfo', countryInfo);
prefs.setString('phone', phone);
prefs.setString('latitude', latitude);
prefs.setString('longitude', longitude);
}
});
}
Loader.hide();
}
void getInfoFromSharedPref() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
address = prefs.getString('address');
countryInfo = prefs.getString('countryInfo');
phone = prefs.getString('phone');
latitude = prefs.getString('latitude');
longitude = prefs.getString('longitude');
});
}
}
I would like to make sure that the first time I enter the page, the isFirstLoading variable is set to false and then calls the getInfo function with the http call while if it is false it takes from the shared preferences.
isFirstLoading is now always true
how could I solve?
I think you're overcomplicating your code. Let me know if this solves your issue.:
class _HomeScreenState extends State<HomeScreen> {
SharedPreferences prefs;
#override
void initState() {
super.initState();
getInfo();
}
// ...
}
Now, the first time this widget is inserted into the tree:
initState() will be called once.
Therefore, getInfo() will be called. getInfo() will make the http call and update the prefs variable using setState, which you have already done.
Whenever the widget is reloaded, the prefs variable will not be lost since it is a stateful widget.
Next, if you would like to save the preference settings locally instead of making an http call every time the user opens the app, you should handle that inside of getInfo() itself. Something like this:
getInfo() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool("isFirstLoading") == false) {
// setState to update prefs variable
} else {
// make http call
// save prefs (optional)
// setState to update prefs variable
}
}
If I undestand correctly, you are trying to only call the getInfo method on the first load, and the getInfoFromSharedPref all the other time.
My suggestion is to save the isFirstLoading bool as a preference like so:
class _HomeScreenState extends State<HomeScreen> {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoading = prefs.getBool("isFirstLoading") ?? true;
#override
void initState() async {
super.initState();
if (isFirstLoading) {
await getInfo();
await prefs.setBool("isFirstLoading", false);
isFirstLoading = false;
} else {
getInfoFromSharedPref();
}
}
Future<http.Response> getInfo() async {
// …
}
void getInfoFromSharedPref() async {
// …
}
}
I have a shared data that contain mobile no of customer,in my profile,that need to be filled with in textfield when i open profile page,i'm getting the data from shard preference data,when i load data to textfield it's throws error
TextEditingController mobile = TextEditingController();
void initState() {
getMobile();
}
Get data From Sharedpreference
Future<String> getMobile() async {
Future notificatinstatus = SharedPrefrence().getUserMobile();
notificatinstatus.then((data) async {
var mobile_no=data;
mobile_no.text=mobile;
return mobile;
});
}
I think is better like this:
var mobileController = TextEditingController();
getMobile() async {
Future notificatinstatus = SharedPrefrence().getUserMobile();
notificatinstatus.then((data) async {
var mobile_no=data;
setState(() {
if(mobile_no.isNotEmpty){
mobileController.text = mobile_no;
}
});
});
}
#override
void initState() {
super.initState();
getMobile();
}
class _CategoriesPageState extends State<CategoriesPage> {
List postsData;
int categoryID;
void getApiData() async {
//Get posts api data
ApiData apiData = ApiData(
apiUrl: 'http://website.com/wp-json/wp/v2/posts?categories=$categoryID',
);
dynamic responseBody = await apiData.getData();
setState(() {
postsData = json.decode(responseBody);
});
}
Now I want to reach the (apiData) in my widget, but because it's inside void function I cant use it.
You have to create a variable outside function, so that you can access by class object.
class _CategoriesPageState extends State<CategoriesPage> {
List postsData;
ApiData apiData
int categoryID;
void getApiData() async {
//Get posts api data
apiData = ApiData(
apiUrl: 'http://website.com/wp-json/wp/v2/posts?categories=$categoryID',
);
dynamic responseBody = await apiData.getData();
setState(() {
postsData = json.decode(responseBody);
});
}
I have been trying to make a app in flutter where an api is called and data is updated in TextField
Used provider for state management, here is the code for it.
class ProfileProvider with ChangeNotifier {
var profileData;
String _url = "http://10.0.2.2:3000/api/v1/user/loggedin_user";
void getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var token = prefs.getString('token');
var data = await http.get(
_url,
headers: {
"accept": "application/json",
"content-type": "application/json",
'Token': token,
},
);
var infoOfPerson = json.decode(data.body);
profileData = new ProfileObject(
name: infoOfPerson['name'],
mobile: infoOfPerson['mobile'],
email: infoOfPerson['email'],
role: infoOfPerson['role'],
);
notifyListeners();
}
ProfileObject get profileInfo {
return profileData;
}
}
I am getting the data fine, now i have to show it in the UI, but sometime data is populated, sometime its not. Can someone please point me the right direction why this is happening.
Here is the code for UI.
class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
final emailController = TextEditingController(text: '');
final nameController = TextEditingController(text: '');
final mobileController = TextEditingController(text: '');
var _isInit = true;
#override
void didChangeDependencies() {
if (_isInit) {
final profileData = Provider.of<ProfileProvider>(context);
profileData.getData();
if (profileData.profileInfo != null) {
emailController.text = profileData.profileInfo.name;
nameController.text = profileData.profileInfo.email;
mobileController.text = profileData.profileInfo.mobile;
}
_isInit = false;
super.didChangeDependencies();
}
}
#override
Widget build(BuildContext context) {
final profileData = Provider.of<ProfileProvider>(context);
return Scaffold(
drawer: NavigationDrawer(),
body: profileData.profileInfo == null
? Center(
child: CircularProgressIndicator(),
)
: Builder(
builder: (context) => SingleChildScrollView(
child: Padding(.....
Below the padding, there is normal TextField, can someone tell me why the data is being populated sometime and sometime its coming empty, even I wrapped it with CircularProgressIndicator() and a check the notifyListeners(); is not working there. The loader is not being shown and data is not being loaded.
Thanks
for StatelessWidget.
Inside the build method use:
Future.microtask(() async {
context.read<SomeProvider>().fetchSomething();
});
For StatefulWidgets if you want to call it once. Do this inside the initState() or didChangeDependencies (better if the latter). This will be called at the end of the frame which means after the build or rendering finishes..
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SomeProvider>().fetchSomething();
});
}
EDIT: WidgetsBinding will also work on build. I forgot on why I used microtask lol
i've created a function which called nextTick, i call it in initState and it works for now, but want to see others method
void nextTick(Function callback, [int milliseconds = 0]) {
Future.delayed(Duration(milliseconds: 0)).then((_) {
callback();
});
}
then use it like below
#override
void initState() {
super.initState();
nextTick((){
ProfileProvider profileProvider = Provider.of<ProfileProvider>(context);
profileProvider.getProfile();
});
}
Edit: i store couple of variables to manage them on ui, like isLoading, hasError and errorMessage. Here is my provider class
class ProfileProvider extends ChangeNotifier {
bool _hasError = false;
bool _isLoading = true;
String _errorMsg = '';
Profile _profileResponse;
bool get hasError => _hasError;
bool get isLoading => _isLoading;
String get errorMsg => _errorMsg;
Profile get profileResponse => _profileResponse;
Future<void> getProfile() async {
this.setLoading = true;
this.setError = false;
this.setErrorMsg = '';
try {
await dio.post('$api/p/view', data: {}).then((res) {
print(res.data);
_profileResponse = Profile.fromJson(jsonDecode(res.data));
print(_profileResponse.data);
notifyListeners();
}).whenComplete(() {
this.setLoading = false;
});
} catch (e) {
this.setError = true;
this.setErrorMsg = '$e';
}
this.setLoading = false;
}
set setError(bool val) {
if (val != _hasError) {
_hasError = val;
notifyListeners();
}
}
set setErrorMsg(String val) {
if (val != null && val != '') {
_errorMsg = val;
notifyListeners();
}
}
set setLoading(bool val) {
_isLoading = val;
notifyListeners();
}
}