Get data from an asynchronous method? - flutter

I am having big time trouble with getting the data from my method getAllPlayerData() outside the .then((Value).
The "bad print" in my code shows up before my "good print". I want to be able to use my variable "TheStats" in my code with all the data inside of it and not a "null" value or "instance of Future<Dynamic"
import 'package:cleanmaybe/Controllers/players_controller.dart';
import 'package:flutter/material.dart';
class PlayerStatView extends StatefulWidget {
const PlayerStatView({Key? key}) : super(key: key);
#override
State<PlayerStatView> createState() => _PlayerStatViewState();
}
class _PlayerStatViewState extends State<PlayerStatView> {
var theStats;
#override
void initState() {
getAllPlayerData().then((value) {
theStats = value;
print("Good print $theStats");
});
super.initState();
}
#override
Widget build(BuildContext context) {
print("bad print $theStats");
return Container();
}
}
This is my "getAllPlayerData()"
getAllPlayerData() async{
SharedPreferences sp = await _pref;
int? id = sp.getInt('idPlayer');
final statAccess = DatabaseModel();
var lookForData = await statAccess.getPlayerData(id);
return lookForData;
}
And if you need it, this is my getPlayerData(id) method that fetches all the data from my database:
Future getPlayerData(idPlayer) async {
var dbCoach = await db;
var dataPlayer = dbCoach!.rawQuery("SELECT * FROM players WHERE"
" idPlayer = '$idPlayer'");
return dataPlayer;
}

Your "bad print" will practically always run before "Good print" as long as getAllPlayerData() take any time at all to process with this construct.
There are several ways to handle this, but a suggestion is to use the FutureBuilder widget for it (as #pskink just wrote as a comment :)) Check the offical documentation.

Related

LateInitializationError with Future

I hope you could help me!
Error saying 'tables' has not been initiliazed. But when I set tables = [] instead of
widget.data.then((result) {tables = result.tables;})
it works. I think the problem comes from my app state data which is a Future.
My simplified code:
class NavBar extends StatefulWidget {
final Future<Metadata> data;
const NavBar({Key? key, required this.data}) : super(key: key);
#override
State<NavBar> createState() => _NavBarState();
}
class _NavBarState extends State<NavBar> {
late List<MyTable> tables;
#override
void initState() {
widget.data.then((result) {
tables = result.tables;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: buildPages(page.p)
)
);
}
Widget buildPages(index){
switch (index) {
case 0:
return ShowTablesNew(tables: tables);
case 1:
return const Details();
case 2:
return const ShowTables();
default:
return const ShowTables();
}
}
}
Future doesn't contain any data. It's an asynchronous computation that will provide data "later". The initialization error happens because the variable 'tables' is marked as late init but is accessed before the future is completed, when in fact it's not initialized yet.
Check this codelab for async programming with dart.
For your code you can use async/await in the initState method doing something like this
String user = '';
#override
void initState() {
asyncInitState();
super.initState();
}
void asyncInitState() async {
final result = await fetchUser();
setState(() {
user = result;
});
}
but since you're using a list of custom objects the most straightforward way is probably to use a FutureBuilder widget

I can succesfully show asset logo but i still get the error: Unable to load asset

I have a use case where i have to load the asset image based on the value of the key logo_image from a json file.
So i have this json with key logo_image and value image1.png:
{
"logo_image" : "image1.png"
}
There is an image inisde the assets folder like this: assets/image1.png
I have a class called GetAssets() with a method called getAssetItems() which loads the json file and another method called getLogo which i will use as blueprint in the view to specify which key item i want to view:
class GetAssets {
Future<Map<String, dynamic>> getAssetItems() async {
String jsonData =
await rootBundle.loadString('assets/items.json');
Map<String, dynamic> data = jsonDecode(jsonData);
return data;
Future getLogo(String key) async {
var assets = await GetAssets().getAssetItems();
return 'assets/' + assets[key];
}
}
I have a field called logo and i have method called setAssets() to load the logo and set the state of the logo with setState() and i use initState() to initialize setAssets(). I have a Scaffold() which i use to view the asset logo with the field logo:
import 'package:flutter/material.dart';
class ShowLogo extends StatefulWidget {
const ShowLogo({Key? key}) : super(key: key);
#override
_ShowLogoState createState() => _ShowLogoState();
}
class _ShowLogoState extends State<ShowLogo> {
late String logo = "";
setAssets() async {
final logoFromAsset = await GetAssets().getLogo("logo_image");
setState(() {
logo = logoFromAsset;
});
}
#override
initState() {
setAssets();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Image.asset(logo),);
}
}
I am able to show the logo on the screen of my phone, but i still get the error: Unable to load asset: What is the cause of this? How can i resolve this issue?
I think this is because of Json asset asynchronous method call.
When the widget executing then build(BuildContext context) method try to build the widget immediately after the initState (setAssets) method call.
For setAssets method's asynchronous call, build(BuildContext context) method already executed before setting logo = logoFromAsset inside setAssets method and the first time logo variable remains with empty string which is not a valid asset path, thats why Image.asset(logo) can not load the asset/image and throw an error.
But after few moment when your setAssets asynchronous method call executed and called setState this time the widget is rebuild with the non-empty/valid asset path and the logo is showing.
Try to set an default image/icon and I think this will resolve your issue.
import 'package:flutter/material.dart';
class ShowLogo extends StatefulWidget {
const ShowLogo({Key? key}) : super(key: key);
#override
_ShowLogoState createState() => _ShowLogoState();
}
class _ShowLogoState extends State<ShowLogo> {
late String logo = "";
setAssets() async {
final logoFromAsset = await GetAssets().getLogo("logo_image");
setState(() {
logo = logoFromAsset;
});
}
#override
initState() {
setAssets();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: logo != "" ? Image.asset(logo) : const Icon(Icons.image)
);
}
}

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())) :

how to access to variable from StatefulWidget to State class flutter

I try to pass data from page one to page two data is pass OK but I have one problem now.
this is my code:
class SecondScreen extends StatefulWidget {
final int itemHolder ;
SecondScreen({Key key, #required this.itemHolder}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new mainState();
}
}
class mainState extends State <SecondScreen> {
bool value = false ;
MyPreferences _myPreferences = MyPreferences();
#override
void initState() {
// TODO: implement initState
super.initState();
initial();
}
void initial() async {
setState(() {
});
}
final String apiURL = 'http://xxxxxxxxx/getFlowersList.php';
Future<List<Flowerdata>> fetchFlowers() async {
var response = await http.get(apiURL);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Flowerdata> listOfFruits = items.map<Flowerdata>((json) {
return Flowerdata.fromJson(json);
}).toList();
return listOfFruits;
}
else {
throw Exception('Failed to load data from Server.');
}
}
I try to use var (itemHolder ) in the link like that:
final String apiURL = 'http://xxxxxxx/getFlowersList.php?id=' +itemHolder;
but I get error:
Undefined name 'itemHolder'. Try correcting the name to one that is defined, or defining the name.
I can access to itemHolder. So how can I access to it?
try this:
...
String apiURL;
#override
void initState() {
super.initState();
apiURL = 'http://xxxxxxx/getFlowersList.php?id=' +widget.itemHolder.toString();
}
...
To use the variables declared in the SecondScreen you have to access it with the prefix 'widget' and the dot operator, not directly. Here is the code:
final String apiURL = 'http://xxxxxxx/getFlowersList.php?id=' + widget.itemHolder;
And when you make the variable final, you have to initialize it while declaration or through constructor else delete keyword final:

How to get id from StatefulWidget in State?

I want to load comments in my post here. For that I need to send post id to my HTTP get request. post id I sent from another page. but I want to assign that String id; value to final response = await http.get("http://$ip:$apiPort/solutions/$id"); here id in Flutter.
How can I do that?
to clear what I want
my code is
class Solutions extends StatefulWidget {
String id ;
final bool isEditMode;
Solutions({
this.id,
this.isEditMode,
});
#override
_SolutionsState createState() => _SolutionsState();
}
class _SolutionsState extends State<Solutions> {
List data;
var ip = Configuration.yourIp;
var apiPort = Configuration.yourApiPort;
Future<List> getData() async {
final response = await http.get("http://$ip:$apiPort/solutions/$id");
return json.decode(response.body);
}
#override
void initState() {
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
Future<List> getData() async {
final response = await http.get("http://$ip:$apiPort/solutions/${widget.id}");
return json.decode(response.body);
}
This should to the trick.
From the State class (_SolutionState in your case), you can access the widget (Solution in your case) members by finding them in widget.
BONUS
Your id should be final, since StatefulWidget is marked as an immutable class, which means its members should all be final. You have surely a warning about this from your IDE.