Data only fetchs when hot reload flutter - flutter

I know this has been asked previously, but I canĀ“t get it by my own.
I have a fetching method:
dynamic getResponse() async {
response = await api.getGlobalResponse();
resp = GlobalResponse.fromJson(jsonDecode(response));
if (resp != null) {
if (resp!.almacenes != null) {
for (Almacen alm in resp!.almacenes!) {
int count = 0;
for (var opt in options) {
if (opt != alm.name) {
continue;
} else {
count += 1;
break;
}
}
if (count <= 0) options.add(alm.name.toString());
}
}
}
return response;
}
which resolves OK after hot reloading.
I call it on the initState() in my first screen after login, like this:
class _LoggedInState extends State<LoggedIn> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
getResponse();
setState(() {
resp = resp;
});
page = getPage();
}
#override
didChangeDependencies() {
getResponse();
setState(() {
resp = resp;
});
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
if (getToken()) {
return SafeArea(
child: Scaffold(
body: page,
),
);
} else {
return getLoginScreen();
}
}
}
The getPage() returns correctly loaded from fetch after hot reload, but I cant figure out how to auto load from the very first time.
I have already tryed ValueNotifiers and ValueListenables, without luck.
willyou please help this fool?

It is generally recommended to avoid using the dynamic type in Dart, as it can lead to potential runtime errors if the wrong type of data is returned or passed in. Instead, it is better to specify the specific type that you are expecting.
If you are sure that you have to use dynamic here, then change return type of getResponse to Future<dynamic> because the function is asynchronous.
In the widget, in initState and didChangeDependencies methods after calling getResponse wait for its response then call setState.
getResponse().then((resp) =>
setState(() {
resp = resp;
}));
This code calls the getResponse method and waits for the response to be returned. Once it is received, it calls the setState method and updates the state of the widget with the returned data.

Related

Flutter GetX state management initial null value

This is what I'm trying to achieve using flutter GetX package but not working properly.
I have a Firestore document, if the document is changed I want to call an api and keep the data up to date as observable.
The code below seems to work but initial screen shows null error then it shows the data.
I don't know how I can make sure both fetchFirestoreUser() and fetchApiData() (async methods) returns data before I move to the home screen.
GetX StateMixin seems to help with async data load problem but then I don't know how I can refresh the api data when the firestore document is changed.
I'm not sure if any other state management would be best for my scenario but I find GetX easy compared to other state management package.
I would very much appreciate if someone would tell me how I can solve this problem, many thanks in advance.
Auth Controller.
class AuthController extends SuperController {
static AuthController instance = Get.find();
late Rx<User?> _user;
FirebaseAuth auth = FirebaseAuth.instance;
var _firestoreUser = FirestoreUser().obs;
var _apiData = ProfileUser().obs;
#override
void onReady() async {
super.onReady();
_user = Rx<User?>(auth.currentUser);
_user.bindStream(auth.userChanges());
//get firestore document
fetchFirestoreUser();
//fetch data from api
fetchApiData();
ever(_user, _initialScreen);
//Refresh api data if firestore document has changed.
_firestoreUser.listen((val) {
fetchApiData();
});
}
Rx<FirestoreUser?> get firestoreUser => _firestoreUser;
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => Login());
} else {
Get.offAll(() => Home());
}
}
ProfileUser get apiData => _apiData.value;
void fetchFirestoreUser() async {
Stream<FirestoreUser> firestoreUser =
FirestoreDB().getFirestoreUser(_user.value!.uid);
_firestoreUser.bindStream(firestoreUser);
}
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
#override
void onDetached() {}
#override
void onInactive() {}
#override
void onPaused() {}
#override
void onResumed() {
fetchApiData();
}
}
Home screen
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Obx(() =>
Text("username: " + AuthController.instance.apiData.username!))),
),
);
}
}
To be honest, I never used GetX so I'm not too familiar with that syntax.
But I can see from your code that you're setting some mutable state when you call this method:
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
Instead, a more robust solution would be to make everything reactive and immutable. You could do this by combining providers if you use Riverpod:
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
final authService = ref.watch(authRepositoryProvider);
return authService.authStateChanges();
});
final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
final userValue = ref.watch(authStateChangesProvider);
final user = userValue.value;
if (user != null) {
// note: this should also be turned into a provider, rather than using a static method
return RemoteService.getProfile(user.uid);
} else {
// decide if it makes sense to return null or throw and exception when the user is not signed in
return Future.value(null);
}
});
Then, you can just use a ConsumerWidget to watch the data:
#override
Widget build(BuildContext context, WidgetRef ref) {
// this will cause the widget to rebuild whenever the auth state changes
final apiData = ref.watch(apiDataProvider);
return apiData.when(
data: (data) => /* some widget */,
loading: () => /* some loading widget */,
error: (e, st) => /* some error widget */,
);
}
Note: Riverpod has a bit of a learning curve (worth it imho) so you'll have to learn it how to use it first, before you can understand how this code works.
Actually the reason behind this that you put your controller in the same page that you are calling so in the starting stage of your page Get.put() calls your controller and because you are fetching data from the API it takes a few seconds/milliseconds to get the data and for that time your Obx() renders the error. To prevent this you can apply some conditional logic to your code like below :
Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) :

Is there any easy way to use a Future (which performs an http connection) inside a stateful widget without having it reconnect on every screen build?

Every time the screen is rebuilt the getJSONfromTheSite seems to get invoked. Is seems because the future is placed inside the Widget build that every time I rebuild the screen it's just calling the apiResponse.getJSONfromTheSite('sitelist') future. But When I try to simply move the apiResponse.getJSONfromTheSite('sitelist') call outside the Widget and into the initState it doesn't work at all.
I'm not fully grasping the interplay of Futures in relation to a stateful widget, but in this case I need to keep the widget stateful because Im using a pull to refresh function to rebuild my state
class _SitelistScreenState extends State<SitelistScreen> {
RemoteDataSource _apiResponse = RemoteDataSource();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
child: FutureBuilder(
future: _apiResponse.getJSONfromTheSite('sitelist'),
builder: (BuildContext context, AsyncSnapshot<Result> snapshot) {
if (snapshot.data is SuccessState) {
AppData sitelistCollection = (snapshot.data as SuccessState).value;
}
},
),
);
}
}
// (Do some UI stuff)
class RemoteDataSource {
//Creating Singleton
RemoteDataSource._privateConstructor();
static final RemoteDataSource _apiResponse =
RemoteDataSource._privateConstructor();
factory RemoteDataSource() => _apiResponse;
MyClient client = MyClient(Client());
void init() {}
Future<Result> getJSONfromTheSite(String call, {counter = 0}) async {
debugPrint('Network Attempt by getJSONfromTheSite');
try {
final response = await client
.request(requestType: RequestType.GET, path: call)
.timeout(const Duration(seconds: 8));
if (response.statusCode == 200) {
return Result<AppData>.success(AppData.fromRawJson(response.body));
} else {
return Result.error(
title: "Error", msg: "Status code not 200", errorcode: 1);
}
} catch (error) {
if (counter < 3) {
counter += 1;
await Future.delayed(Duration(milliseconds: 1000));
return getJSONfromTheSite(call, counter: counter);
} else {
return Result.error(
title: "No connection", msg: "Status code not 200", errorcode: 0);
}
}
}
void dispose() {}
}
A FutureBuilder, as the name suggests, wants to build you something using a FUTURE value that you provide. For that to happen, you should perform an operation outside the build method (for example, in the State class or in the initState function) and store its Future value (like a promise in javascript), to be used later on the FutureBuilder.
You have access to this value inside the FutureBuilder on the snapshot.data variable, as I can see you already know by looking at your code. The way I coded the following solution, you should no longer have issues about multiple requests to the website each time it builds the widget UI (getJSONfromTheSite will only be called once and the result from this call will be available to you inside the FutureBuilder!)
The solution:
class _SitelistScreenState extends State<SitelistScreen> {
RemoteDataSource _apiResponse = RemoteDataSource(); // I left this here because I'm not sure if you use this value anywhere else (if you don't, simply delete this line)
// when creating the widget's state, perform the call to the site once and store the Future in a variable
Future<Result> _apiResponseState = RemoteDataSource().getJSONfromTheSite('sitelist');
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
child: FutureBuilder<SuccessState>(
future: _apiResponseState,
builder: (BuildContext context, AsyncSnapshot<Result> snapshot) {
if (snapshot.data is SuccessState) {
AppData sitelistCollection = (snapshot.data as SuccessState).value;
}
},
),
);
}
}
EDIT: Edited answer to use Result as the inner type of the Future (instead of SuccessState).
The FutureBuilder's behavior can be expected as following according to the documentation
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies.
It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder.
If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
As stated above, if the future is created at the same time as the FutureBuilder, the FutureBuilder will rebuilt every time there's change from the parent. To avoid this change, as well as making the call from initState, one easy way is to use another Widget call StreamBuilder.
An example from your code:
class RemoteDataSource {
final controller = StreamController<AppData>();
void _apiResponse.getJSONfromTheSite('sitelist') {
// ... other lines
if (response.statusCode == 200) {
// Add the parsed data to the Stream
controller.add(AppData.fromRawJson(response.body));
}
// ... other lines
}
In your SiteListScreen:
class _SitelistScreenState extends State<SitelistScreen> {
RemoteDataSource _apiResponse = RemoteDataSource();
#override
void initState() {
super.initState();
_apiResponse.getJSONfromTheSite('sitelist');
}
#override
Widget build(BuildContext context) {
return Scaffold(
child: StreamBuilder<AppData>(
stream: _apiResponse.controller.stream, // Listen to the Stream using StreamBuilder
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
AppData sitelistCollection = snapshot.data;
}
},
),
);
}
This StreamBuilder is a popular concept through out most of Flutter's apps nowadays (and is the basis of many Flutter's architecture), so it's a good idea to take a good look and use the best of it.
There is a simple way you do not need to change too much coding. Like
class RemoteDataSource {
Result _result;
//Creating Singleton
RemoteDataSource._privateConstructor();
static final RemoteDataSource _apiResponse =
RemoteDataSource._privateConstructor();
factory RemoteDataSource() => _apiResponse;
MyClient client = MyClient(Client());
void init() {}
Future<Result> getJSONfromTheSite(String call, {counter = 0}) async {
debugPrint('Network Attempt by getJSONfromTheSite');
if (_result != null) {
return _result;
}
try {
final response = await client
.request(requestType: RequestType.GET, path: call)
.timeout(const Duration(seconds: 8));
if (response.statusCode == 200) {
_result = Result<AppData>.success(AppData.fromRawJson(response.body));
return _result;
} else {
return Result.error(
title: "Error", msg: "Status code not 200", errorcode: 1);
}
} catch (error) {
if (counter < 3) {
counter += 1;
await Future.delayed(Duration(milliseconds: 1000));
return getJSONfromTheSite(call, counter: counter);
} else {
return Result.error(
title: "No connection", msg: "Status code not 200", errorcode: 0);
}
}
}
void dispose() {}
}
I only store the success result to _result, I do not sure that you want store the error result. When you rebuild the widget, it will check if it already get the success result. If true, return the stored result, it not, call api.

Unknown LateInitializationError in Flutter

I have a flutter widget which retrieves Json information from an API and displays a widget of that information. My initState is as follows:
class _BuildCardsWidgetState extends State<BuildCardsWidget> {
bool display = false;
late Resource _resource;
Future<Resource> fetchResource(Build build) async {
var url = Uri.parse(APIURL); <-actual url link hidden
final response = await http.get(url);
var resourceReturn = json.decode(response.body);
var resourceJson = resourceReturn[0]['resource'];
return Resource.fromJson(resourceJson, build);
}
#override
void initState() {
fetchResource(widget.build).then((value) =>
this.setState(() {
_resource = value;
display = true;
}));
super.initState();
}
#override
Widget build(BuildContext context) {
return ... <- _resource is used within build
However, whenever the program gets to the line
final response = await http.get(url);
This error is thrown:
The following LateError was thrown building BuildCardsWidget(dirty, state: _BuildCardsWidgetState#b3c7a):
LateInitializationError: Field '_resource' has not been initialized.
The thing that confuses me is this error flashes, but then right after the widget is correctly built and it runs smoothly. Any suggestions on how to fix this or on how to ignore this error since it does appear to work after the initial error?
Try to avoid using asynchronous functions inside initState, if it's not properly handled with await in a separate function, the widget won't load your data before build your widgets. A quick and simple way to solve this is to use a FutureBuilder().
class _BuildCardsWidgetState extends State<BuildCardsWidget> {
Future<Resource> fetchResource(Build build) async {
var url = Uri.parse(APIURL); <-actual url link hidden
var resourceReturn = json.decode(response.body);
var resourceJson = resourceReturn[0]['resource'];
return Resource.fromJson(resourceJson, build);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<Resource>(
future: fetchResource(build),
builder: (context, snapshot) {
return SizedBox();
}
if(snapshot.hasError) {
/// Handle error
}
}
);
}
}

How to print the correct value from future instance in flutter

I am trying to fetch a field name "total" from mongodb and add all the total for a particular user as shown below
getCartTotal() async{
var total = 0;
Db db = new Db("mongodb://3.133.123.227/Kartofilldatabasetest");
await db.open();
var collection = db.collection('cart');
await collection.find({"customerId":phoneNumber}).forEach((v) {
total = total + v["total"];
});
db.close();
return total;
}
I am trying to use the returned value of total as follow
in the init function i am calling it everytime the page is opened.
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await getTotal();
});
}
getTotal() async {
totalCartValue = await Mongocart(phoneNumber: widget.phoneNumber,description: '',image: '',price: '',quantity: '',total: '').getCartTotal();
}
#override
Widget build(BuildContext context) {
print(totalCartValue);
return Container();
}
}
Whenever i am trying to print the value it shows instance of future and not the value is printed.
This is what is getting printed Instance of 'Future'
Below is my database screenshot
I want fetch the field total of all the object and assign it in the totalcartvalue currently it is printing 0.
Since you are using async in the getTotal() function, the result should be using await, otherwise it would just build concurrently with the data is being retrieve, thus no data is displayed on build.
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await getTotal();
});
}

Why this shake functionality not working in flutter?

This ShakePlugin is not working with this piece of code,when im just using this code without these api calls and all its working fine.
class MyAppState extends State<MyApp> {
List data;
String _search = 'nature';
int index = 0;
File imageFile;
String imageData;
bool dataLoaded;
var path;
int count = 10;
FlutterShakePlugin _shakePlugin;
void initState() {
super.initState();
_shakePlugin = FlutterShakePlugin(
onPhoneShaken: () {
setState(() {
count=count+10;
});
},
},
)..startListening();
}
void dispose() {
super.dispose();
_shakePlugin.stopListening();
}
Future<String> getjsondata() async {
try {
var response = await http.get(
'https://api.unsplash.com/search/photos?per_page=${count}&client_id=TcAQEO3JoMG90U7Rl-YUiDo1x9XbZukzMOMQhxUVCV4&query=${_search}');
setState(() {
var converted = json.decode(response.body);
data = converted['results'];
});
} catch (e) {}
return 'success';
}
void saveImage(int i) async {
var url = data[i]['urls']['small'].toString();
var imageId = await ImageDownloader.downloadImage(url);
path = await ImageDownloader.findPath(imageId);
}
#override
Widget build(BuildContext context) {
getjsondata();
return GestureDetector(
child: SwipeDetector(
child: Container(
child: Image.network(
data[index]['urls']['small'],
I want to increase the count of images i recieve from api on shake of screen but this is not working even if i have installed all the libraries and all.
Calling your getjsondata method in the build method will cause the ui to render infinitely because you're calling setState in getjsondata. I think the shake plugin is working fine but its result is void because the screen is in an infinite render state.
If you move getjsondata to a FutureBuilder, remove the setState call from inside the getjsondata method and render your ui on the result of the Future your code should work.