Flutter: How can I access element from API in my UI - flutter

I successfully get data from API. I test it using the Print command and the element displayed successfully in the console. When I tried to access the same element in TEXT Widget it displays null.
Here is the code:
import 'package:api_practice/worker/data.dart';
import 'package:flutter/material.dart';
import 'package:api_practice/worker/data.dart';
class WeatherHome extends StatefulWidget {
#override
_WeatherHomeState createState() => _WeatherHomeState();
}
class _WeatherHomeState extends State<WeatherHome> {
TextEditingController searchcont = TextEditingController();
DataClass datainstace = DataClass();
void data() async {
await datainstace.getcurrentlocation();
await datainstace.getdata();
print(datainstace.area);
}
#override
void initState() {
data();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Text(
("${datainstace.area}"),
style: TextStyle(fontSize: 25, color: Colors.white),
),
);
}
}
output on console: output in console
App looks like: appscreen

In addition to the previous answers, you can use a value listenable builder, this improves performance and thus it is not necessary to use a setState.
You can check the following link to obtain information on how to use it
Explore ValueListenableBuilder in Flutter

You should use setState method in data function. Because your build method is finishing before your async method.
Use it like this:
import 'package:api_practice/worker/data.dart';
import 'package:flutter/material.dart';
import 'package:api_practice/worker/data.dart';
class WeatherHome extends StatefulWidget {
#override
_WeatherHomeState createState() => _WeatherHomeState();
}
class _WeatherHomeState extends State<WeatherHome> {
TextEditingController searchcont = TextEditingController();
DataClass datainstace = DataClass();
void data() async {
await datainstace.getcurrentlocation();
await datainstace.getdata();
print(datainstace.area);
setState(() {});
}
#override
void initState() {
data();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Text(
"${datainstace.area}",
style: TextStyle(fontSize: 25, color: Colors.white),
),
);
}
}

when you want to update the state of your widget, you have to call the setState function to make the widget re-render with the changes.
void data() {
setState(() async {
await datainstace.getcurrentlocation();
await datainstace.getdata();
print(datainstace.area);
});
}

Related

How to use a variable within a method elsewhere in Flutter?

I know this is a stupid question, but I'm asking as a newbie to flutter.
I created a getData() method to call Firebase's User data and display it on the app. And to call it, result.data() is saved as a variable name of resultData.
But as you know I can't use Text('user name: $resultData'). How do I solve this? It's a difficult problem for me, since I don't have any programming basics. thank you.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shipda/screens/login/login_screen.dart';
import 'package:get/get.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _authentication = FirebaseAuth.instance;
User? loggedUser;
final firestore = FirebaseFirestore.instance;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
#override
void initState() {
super.initState();
getCurrentUser();
getData();
}
void getCurrentUser(){
try {
final user = _authentication.currentUser;
if (user != null) {
loggedUser = user;
print(loggedUser!.email);
}
} catch (e){
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Text('Home Screen'),
IconButton(
onPressed: () {
FirebaseAuth.instance.signOut();
Get.to(()=>LoginScreen());
},
icon: Icon(Icons.exit_to_app),
),
IconButton(
onPressed: () {
Get.to(() => LoginScreen());
},
icon: Icon(Icons.login),
),
Text('UserInfo'),
Text('user name: ')
],
),
),
);
}
}
What you are referring to is called state.
It is a complex topic and you will have to start studying it to correctly develop any web based app.
Anyway, as for your situation, you should have resultData be one of the attributes of the _HomeScreenState class.
Then change resultData in a setState method, like this:
setState(() {
resultData = result.data();
});
Then, in the Text widget, you can actually do something like:
Text("My data: " + resultData.ToString())
Instead of ToString of course, use anything you need to actually access the data.
By writing
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
you make resultData only local to the function getData(). You should declare it outside. Also you need to put it in a setState to make it rebuild the screen after loading. I don't know what type it is, but if it's a String for example you could write
String? resultData;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
setState(() {
resultData = result.data();
});
}
Then you can use Text('user name: $resultData') for example

Flutter async methods for widget initialize

Let's say I create a new screen team_screen which is the first parent of the tree.
Now for my team screen there are many widgets, some of theme have their own request, I want to show loader until every widget/request finished and ready.
I thought on 2 approaches.
All the requests are executed in team_screen with future builder and I pass the props to my widgets by demand.
Every widget with request get function that get executed in the async function in the initState function, then in my parent I make to every widget state parameter that is equal to true by the function I passed and when all is don't I stop the loader.
To sum up my problem is how to maintain a widget with many children and requests and showing one loader for entire page, making all the request on same widget? Pass isInitialize function to every widget?.
Which approach is better and if there are more approaches, I would like to hear.
Thank you for your help
Example for the second approach:
import 'package:flutter/material.dart';
import 'package:info_striker/locator.dart';
import 'package:info_striker/models/fixture/fixture.dart';
import 'package:info_striker/models/odds/bookmaker.dart';
import 'package:info_striker/models/synced-team/synced_team.dart';
import 'package:info_striker/services/fixture_service.dart';
import 'package:info_striker/utils/date_utilities.dart';
class TeamNextMatch extends StatefulWidget {
Function isInitialized;
SyncedTeam team;
TeamNextMatch({
Key? key,
required this.isInitialized,
required this.team,
}) : super(key: key);
#override
State<TeamNextMatch> createState() => _TeamNextMatchState();
}
class _TeamNextMatchState extends State<TeamNextMatch> {
Fixture? _fixture;
Bookmaker? _matchResult;
bool _isInitialized = false;
#override
void initState() {
super.initState();
init();
}
init() async {
final response = await locator<FixturesService>().getData(widget.team.id);
if (response != null) {
setState(() {
_fixture = Fixture.fromMap(response["fixture"]);
_matchResult = Bookmaker.fromMap(response["matchResultOdds"]);
});
}
widget.isInitialized(true);
}
#override
Widget build(BuildContext context) {
String? _date;
bool show = _fixture != null && _matchResult != null;
_fixture != null ? "${DateUtilities.getShortDateString(_fixture!.date)}, ${DateUtilities.getTimeString(_fixture!.date)}" : null;
return show
? Column(
children: [
Text(_fixture?.league?["name"]),
if (_date != null) Text(_date),
],
)
: const SizedBox();
}
}
You can show loader as described below -
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_application_1/data_model.dart';
import 'package:http/http.dart' as http;
class APiTest extends StatefulWidget {
const APiTest({Key? key}) : super(key: key);
#override
_APiTestState createState() => _APiTestState();
}
class _APiTestState extends State<APiTest> {
final String _url = "https://jsonplaceholder.typicode.com/todos/";
bool _isLoading = true;
final List<DataModel> _allData = [];
#override
void initState() {
super.initState();
_initData().then((value) {
setState(() {
_isLoading = false;
});
});
}
Future<void> _initData() async {
final response = await http.get(Uri.parse(_url));
final List res = jsonDecode(response.body);
res.forEach((element) {
_allData.add(DataModel.fromJson(element));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Loading Demo"),
),
body: Stack(
children: [
ListView.separated(
itemCount: _allData.length,
controller: ScrollController(),
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: ((context, index) {
return ListTile(
tileColor: Colors.grey[200],
title: Text(_allData[index].title!),
subtitle: Text(_allData[index].id.toString()),
);
}),
),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
)
],
),
);
}
}

What is the correct way to execute initial network request in Flutter?

I have the following code, to get initial data for the screen and this SchedulerBinding seems to be a hack, but if I remove it, request data is lost.
I think it happens due to the fact widgets(streamBuilders etc.) are not yet built.
Any ideas how can I fix this?
Full screen code: https://gist.github.com/Turbozanik/7bdfc69b36fea3dd38b94d8c4fcdcc84
Full bloc code: https://gist.github.com/Turbozanik/266d3517a297b1d08e7a3d7ff6ff245f
SchedulerBining is not a hack,according to docs addPostFrame call callback only once and if you remove it your stream will never get the data
but you can call your stream loading in iniState
void initState(){
super.initState();
_mblock.loadSpotMock();
}
You can load your data asynchronously in the initState method, meanwhile you can show a loader or message. Once your data has loaded, call setState to redraw the widget.
Here is an example of this:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
String _data;
Future<String> loadData() async {
// Simulate a delay loading the data
await Future<void>.delayed(const Duration(seconds: 3));
// Return the data
return "This is your data!";
}
#override
initState() {
super.initState();
// Call loadData asynchronously
loadData().then((s) {
// Data has loaded, rebuild the widget
setState(() {
_data = s;
});
});
}
#override
Widget build(BuildContext context) {
if (null == _data) {
return Text("Loading...");
}
return Text(_data);
}
}
You can test it in https://dartpad.dartlang.org
It works like this:
initState will call loadData asynchronously, then the build method will draw the widget.
when loadData returns, the call to setState will redraw the widget.
Using StreamBuilder
The following example uses a StreamBuilder to show the data, once it's loaded:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
// Create a stream and execute it
final Stream<String> _myStream = (() async* {
// Simulate a delay loading the data
await Future<void>.delayed(const Duration(seconds: 3));
// Return the data
yield "This is your data!";
})();
#override
Widget build(BuildContext context) {
return StreamBuilder<String>(
stream: _myStream,
builder: (BuildContext context, s) {
String result;
if (s.hasError) {
result = "Error";
}
else {
if (s.connectionState == ConnectionState.done) {
result = s.data;
}
else {
result = "Loading...";
}
}
return Text(result);
}
);
}
}
Hope this helps :)

How can I pass parameters to listeners in Flutter?

I'm new in Flutter and I am following this official example about text fields: https://flutter.dev/docs/cookbook/forms/text-field-changes
There is an axample for listen to changes in the controller of a text field widget. Please note this fragment of code _MyCustomFormState
final myController = TextEditingController();
#override
void initState() {
super.initState();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
If I have two fields and two controllers, I would like to have just one listener, and display some message depending on which controller called the method. I would like to do something like this:
final myController1 = TextEditingController();
final myController2 = TextEditingController();
#override
void initState() {
super.initState();
myController1.addListener(_printLatestValue('message1'));
myController1.addListener(_printLatestValue('message2'));
}
_printLatestValue(message) {
print("Second text field: ${myController.text + message}");
}
which is not possible because the method addListener() uses some called VoidCallback, which have no arguments. At least that is what I understood from the Flutter docs.
So, if it is possible, how can I achieve what I'm looking for?
You're almost correct, but not quite. You're free to pass in any arguments to the listener. However, those arguments need to come from somewhere else - TextEditingController does not supply any, and it does not expect any return values. In other words, the signature should be something like: () => listener(...).
So to answer your question, you're free to do something like the following to distinguish the controllers:
void initState() {
super.initState();
firstController.addListener(() => _printLatestValue('first'));
secondController.addListener(() => _printLatestValue('second'));
}
Full working example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text controllers',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final firstController = TextEditingController();
final secondController = TextEditingController();
void initState() {
super.initState();
firstController.addListener(() => _printLatestValue('first'));
secondController.addListener(() => _printLatestValue('second'));
}
#override
void dispose() {
firstController.dispose();
secondController.dispose();
super.dispose();
}
_printLatestValue(message) {
if (message == 'first') {
print('Received form first controller: ${firstController.text}');
} else {
print('Received from second controller: ${secondController.text}');
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Controllers', style: TextStyle(fontSize: 18)),
),
body: Center(
child: Column(
children: <Widget>[
TextField(controller: firstController,),
TextField(controller: secondController,)
],
),
),
);
}
}
Note that in this case, listener will only print the text from a TextField that was changed.

Flutter function called infinitely in build

I am making an app which loads the CSV and show the table on the screen but the load function is being called infinitely in the build state can anyone know how to fix it I wanted to call only once but my code called it many times.
Here is the console screenshot:
Here is the code:
import 'package:flutter/material.dart';
import 'package:csv/csv.dart';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
class TableLayout extends StatefulWidget {
#override
_TableLayoutState createState() => _TableLayoutState();
}
class _TableLayoutState extends State<TableLayout> {
List<List<dynamic>> data = [];
loadAsset() async {
final myData = await rootBundle.loadString("asset/dreamss.csv");
List<List<dynamic>> csvTable = CsvToListConverter().convert(myData);
return csvTable;
}
void load() async{
var newdata = await loadAsset();
setState(() {
data = newdata;
});
print("am i still being called called ");
}
#override
Widget build(BuildContext context) {
load();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Apps"),),
//floatingActionButton: FloatingActionButton( onPressed: load,child: Icon(Icons.refresh),),
body: ListView(
children: <Widget>[
Container(margin: EdgeInsets.only(top: 20.0),),
Table(
border: TableBorder.all(width: 1.0,color: Colors.black),
children: data.map((item){
return TableRow(
children: item.map((row){
return Text(row.toString(),style: TextStyle(fontSize: 20.0,fontWeight: FontWeight.w900),);
}).toList(),
);
}).toList(),
),
]),
));
}
}
Here is the solution.
#override
void initState() {
super.initState();
load(); // use it here
}
#override
Widget build(BuildContext context) {
return MaterialApp(...); // no need to call initState() here
}