Running Multiple Async Requests at Once in Dart. Proper way - flutter

I am trying to work with concurrency in Flutter, so I have three get-request from server and I need to get values from them at the same time. Each request was parsed into a model. Now I'm trying to figure out how I can combine all the models into one list with three get-request and run this the final list in ListView.builder. Also my task is quite hard for such noobie as I am, beacuse besides parsed concurrently all three lists I need to filter them out, because as said in API all requests are nested and depends on id. How can i resolve this?
This is my models:
ScheduleVariants{
final int mrId;
final int mvId;
ScheduleVariants({this.mrId, this.mvId});
}
FlightCard{
final int mvId;
final int stId;
FlightCard({this.mrId, this.stId});
}
Stop{
final int stId;
Stop({this.stId})
}
I need to get final values from Stop models. As you can see all models have nested stucture and I can't avoid this.
Now I am trying to make concurrent call like this:
class Dire extends StatefulWidget {
final int mrId;
final int mvId;
final int stId;
const Dire({Key key, this.mrId, this.mvId, this.stId}) : super(key: key);
#override
_DireState createState() => _DireState();
}
class _DireState extends State<Dire> {
#override
void initState() {
fetchData();
super.initState();
stops.where((element) => element.stId == widget.stId).toList();
card.where((element) => element.mvId == widget.mvId).toList();
sheduler.where((element) => element.mrId == widget.mrId).toList();
}
List<ScheduleVariants> sheduler;
List<FlightCard> card;
List<Stop> stops;
Future fetchData() async {
String username = '';
String password = '';
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
final result = await Future.wait([
http.get(
Uri.parse(
"http://mysecurelink/getMarshVariants.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getFlightCard.php?fmt=json&mv_id"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getStops.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
]);
setState(() {
sheduler = json.decode(result[0].body) as List;
card = json.decode(result[1].body) as List;
stops = json.decode(result[2].body) as List;
});
}
#override
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: fetchData(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: stops.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(stops[index].stTitle),
);
});
} else {
return CircularProgressIndicator();
}
}));
}
}
At the end of the main task is to run three parallel request filtered by ID and get data from the Stops model. How can you do it right?
I am noob and don't get how properly to do it and I will really glad if someone help me to figure this task out.

I am going to make some assumptions, because there is not enough information:
Dire is a combination of the three classes ScheduleVariants, FlightCard and Stop where ScheduleVariants.mvId == FlightCard.mvId and FlightCard.stId == Stop.stId.
All three APIs will return a list as their response.
All ScheduleVariants have unique mvId, all FlightCards have unique mvId and stId, and all Stops have unique stId.
There is nothing wrong with the way you execute multiple asynchronous requests. Future.wait in this case takes in a list of futures and returns a list of responses. The problem you are facing is just that you do not know how to merge the responses from the three API requests.
You seem to also be mixing up the use of state with the use of futures. At least in the code snippet you provided, it does not seem like you ever need to change the state after you initialize it, which means you do not need to use state at all.
Dire should just be a model class.
class Dire {
final ScheduleVariants scheduleVariant;
final FlightCard flightCard;
final Stop stop;
Dire(this.scheduleVariant, this.flightCard, this.stop);
}
In your widget where you want to get the Dires from the APIs, you can use this function in the FutureBuilder:
Future<List<Dire>> fetchData() async {
String username = '';
String password = '';
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
final result = await Future.wait([
http.get(
Uri.parse(
"http://mysecurelink/getMarshVariants.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getFlightCard.php?fmt=json&mv_id"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getStops.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
]);
flightCardMap = HashMap.fromIterable(json.decode(result[1].body), (fc) => fc["mvId"], (fc) => FlightCard(fc));
stopMap = HashMap.fromIterable(json.decode(result[2].body), (s) => s["stId"], (s) => Stop(s));
return json.decode(result[0].body).map((sv) => {
flightCard = flightCardMap[sv["mvId"]];
return Dire(ScheduleVariants(sv), flightCard, stopMap[flightCard["stId"]]);
}).toList();
}
A disclaimer: I did not check this code snippet for syntax errors, so there might be some but the general idea is there.

Related

call parameter function to get data in flutter

I'm learning and trying to add parameters when calling parameters in functions when getting data from the API, but I'm a bit confused about how I call them in widgets.
static Future<Map<String, DataKuliahModel>> getDataKuliah(String smt) async {
String url = Constant.baseURL;
String token = await UtilSharedPreferences.getToken();
await Future.delayed(const Duration(milliseconds: 1000));
// String responseJson = await rootBundle.loadString('assets/1.json');
Map<String, DataKuliahModel> finalResult = {};
final response = await http.get(
Uri.parse(
'$url/auth/mhs_siakad/perwalian/get_paket',
),
headers: {
'Authorization': 'Bearer $token',
},
);
final result = jsonDecode(response.body)['data'] as Map<String, dynamic>;
result.forEach((key, value) {
DataKuliahModel dataKuliah = DataKuliahModel.fromMap(value);
finalResult.addAll({
key: dataKuliah,
});
});
return finalResult;
}
and I want to call him here
When you declare a function with positional parameters you need to provide those parameters when you call that function.
import 'package:flutter/material.dart';
class Services {
static Future<String> greeting(String name) async {
/// this function doesn't need to be Future
/// but when you call API to get some data it should be a Future
return 'Hello $name';
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
#override
Widget build(BuildContext context) {
return FutureBuilder(
/// pass positional parameter to [greeting] here
future: Services.greeting('Dash'),
builder: (context, AsyncSnapshot<String> snapshot) {
return Center(
child: Text(snapshot.data ?? 'default'),
);
},
);
}
}
Result: Hello Dash
In your case smt seems to be an int not a String
and you have to pass it as query parameter to http request as follows
static Future<Map<String, DataKuliahModel>> getDataKuliah(int smt) async {
String url = Constant.baseURL;
String token = await UtilSharedPreferences.getToken();
await Future.delayed(const Duration(milliseconds: 1000));
// String responseJson = await rootBundle.loadString('assets/1.json');
Map<String, DataKuliahModel> finalResult = {};
final response = await http.get(
// Uri.parse(
// '$url/auth/mhs_siakad/perwalian/get_paket',
// ),
Uri.http(url, '/auth/mhs_siakad/perwalian/get_paket',
{'smt':smt}),
headers: {
'Authorization': 'Bearer $token',
},
);
final result = jsonDecode(response.body)['data'] as Map<String, dynamic>;
result.forEach((key, value) {
DataKuliahModel dataKuliah = DataKuliahModel.fromMap(value);
finalResult.addAll({
key: dataKuliah,
});
});
return finalResult;
}
Have you looked at the Uri replace method?
You can do the following:
Uri.parse('$url/auth/mhs_siakad/perwalian/get_paket').replace(queryParameters:{ "smt":"$smt"});
Update on FutureBuilder:
// Put this outside your build function
Future<Map<String, DataKuliahModel>> DK ;
// Put this in your initState if you want the future to run on page load or use it for events like onTap
DK = Service.getDataKuliah(<PARAM>);
// This is in your build method
FutureBuilder(
future:DK,
builder: (context, snapshot) {
// add wigets to display results here
}
)

How can I display a logged in user details in flutter

I have used get method to retrieve user details and have got 200 status as well. I am having confusion how to show the details in UI. In my homepage I have a floating action button which leads to the profile page. Any help would be much appreciated Thank you.
Future getProfile() async {
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
print(response.statusCode);
if (response.statusCode == 200) {
if (response.body != "") {
var results = json.decode(response.body);
var resultData = results['data']['name'];
print(resultData);
}
}
}
you can use a FutureBuilder like this:
FutureBuilder<dynamic>(
future: getProfile,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}
},
);
...
Future getProfile() async {
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
dynamic resultData;
if (response.statusCode == 200) {
if (response.body != "") {
var results = json.decode(response.body);
resultData = results['data']['name'];
print(resultData);
}
}
return resultData;
}
When you are working with network data (i.e. API responses), the best practice states that you should convert the received data into Dart objects. You will then be able to easily access your data.
Quick and easy approach (not recommended)
For a quick and dirty approach, you could do the following:
1- create a model for your user. Create new file and name it user_model.dart
class User{
String id;
String name;
// Add whatever other properties you need to pull from the server here
User({
this.id,
this.name,
});
// This function will help you convert the deata you receive from the server
// into an instance of User
factory User.fromJson(Map<String, dynamic> json) => User({
id: json['id'],
namne: json['name']
})
}
2- Instanciate a new user in your getProfile() function
Future<User?> getProfile() async { // you want to get a Future<User>
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
print(response.statusCode);
if (response.statusCode == 200) {
if (response.body != "") {
var result = json.decode(response.body)['data']; // select the data you need here
final user = User.fromJson(result) // create a new User instance
return user // return it
}
}
// in case something went wrong you want to return null
// you can always check the nullity of your instance later in your code
return null;
}
3- In your UI, you can consume the newly created instance like so. I am assuming you are inside a build() function of any widget!
//...
#override
Widget build(BuildContext context) {
return FutureBuilder<dynamic>(
future: getProfile,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.hasData){
final user = snapshot;
// now you can access your user's data as you wish
print(user.id);
print(user.name);
}
);
}
//...
Better Approach (recommended)
The above approach, although seems to work, won't be ideal for a more complex project. For that, you want to follow a road map that could look like the following:
Automate object serialization/deserialization using packages like freezed. This will offload you from any unwanted error injection by building toJson and fromJson methods, among others, for you ;). Check their documentation for more details.
Manage data streams using a state management library like bloc. You can access your state, in your case the user's profile data, from anywhere in the widget tree without having to use FutureBuilder everywhere. It will also help you keep in sync with your data. Check their well-written documentation for more details on how to use it.
I have mentioned these two libraries here because they are the ones I work with all the time and that I am familiar with. They might be others out there that do more or less the same. It's up to you to pick whichever you feel comfortable with ;)
Once you get familiar with a state management library you could architect your app as follow:
/...
-lib
|- model #build your data instance and return object
|- repository #call API methods and convert received data to model instance
|- api #make HTTP calls
|- ui #build UI elements
|- bloc #receive events from UI and call repository functions then return datastreams to UI

Flutter where to put http.get

I am making lecture room reservation system.
class SearchView2 extends StatefulWidget {
#override
_SearchViewState2 createState() => _SearchViewState2();
}
class _SearchViewState2 extends State<SearchView2> {
String building = Get.arguments;
List data = [];
String roomID = "";
int reserved = 0;
int using = 0;
Future<String> getData() async {
http.Response res = await http.get(Uri.parse(
"https://gcse.doky.space/api/schedule/classrooms?bd=$building"));
http.Response res2 = await http.get(Uri.parse(
"https://gcse.doky.space/api/reservation/currtotal?bd=$building&crn=$roomID"));
reserved = jsonDecode(res2.body)["reserved"];
using = jsonDecode(res2.body)["using"];
this.setState(() {
data = jsonDecode(res.body)["result"];
});
return "success";
}
#override
void initState() {
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('강의실 선택')),
body: new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
roomID = data[index];
return new Card(
child: ListTile(
onTap: () async {}, title: Text(data[index] + " " + reserved)),
);
},
),
);
}
}
I want to get 'using' and 'reserved' data and print them in the list view.
But roomID is in Listview
I want data[index] as roomID but with my code roomID will be null, so it won't print the result.
Where should I move http.Response res2? (not res)
Or is there other way to get using and reserved data in the listview?
First of all, you have a single building and multiple rooms in that building. So, fetching a building data along with the data of all it's rooms together will take too much time.
Instead, you can break it into two parts.
For fetching Building data,
Future<List<String>> getData() async {
http.Response res = await http.get(Uri.parse("https://gcse.doky.space/api/schedule/classrooms?bd=$building"));
return (jsonDecode(res.body)["result"] as List)
.map<String>((e) => e.toString())
.toList();
}
Then, for fetching each room data, Here you have to pass roomID.
Future<Map<String, dynamic>> getRoomData(String roomID) async {
http.Response res2 = await http.get(Uri.parse("https://gcse.doky.space/api/reservation/currtotal?bd=$building&crn=$roomID"));
return {
'reserved': jsonDecode(res2.body)["success"]["reserved"],
'using': jsonDecode(res2.body)["success"]["using"],
};
}
Now, you can use FutureBuilder widget to build something that depends on fetching data asynchronously.
You also don't need a StatefulWidget since you are using FutureBuilder and can remove all unnecessary local variables you have defined.
Here is the full working code. PasteBin Working Code.
Just replace your entire SearchView2 code with the code in the link.
This is the output.

How to filter's by id in Dart? Not searching bar

guys, I am trying to make filter by using id's of item. I have researched a lot, but I think I am missing something obvious. So as I said i need to make a filtration, I have API where two different models Transport and Marshes. Each clasess contains id of bus, tram, subway. For the bus id = 1, for tram = 2, for sunbway id = 3 and for each of these separate id's API contains the 3 separate list of bus' numbers, tram's numbers and subway's train numbers.
I create two models and two screen:
The models
class TransportType {
TransportType({
this.ttId,
this.ttTitle,
this.ttNote,
});
final int ttId;
final String ttTitle;
String ttNote;
}
Also I also clearly indicated that I need the ID of the route in the link from the API. Now it look like so:
For Marshes it is:
Future<List<Marshes>> fetchMarshes() async {
final response = await http.post(
Uri.parse(
'http:/getMarshes.php?fmt=json&tt_id'),
body: {'tt_id': '1'}, headers: <String, String>{'authorization': basicAuth}, );
For fetching Transport model it looks like this:
Future<List<TransportType>> fetchTranspotes() async {
var response = await http.get(
Uri.parse(
'http://getTransportTypes.php?fmt=json'),
headers: <String, String>{'authorization': basicAuth});
var jsonResponse = convert.jsonDecode(response.body) as List;
return jsonResponse
.map((transport) => TransportType.fromJson(transport))
.toList();
}
For fetching the both list: List of Transport types and List of Numbers I am using FutureBuilder:
The srceen one for Transport Types looks like so:
body: FutureBuilder<List<TransportType>>(
future: futureTransposrt,
builder: (context, snapshot) {
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (builder, index){
TransportType project = snapshot.data[index];
return ListTile(
title: Text(project.ttId.toString()),
onTap: (){
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context)=>MarshId(ttId: 1,)), (route) => false);
},
In the conctructor of MarshId I put the values I want, so the second sreen also has the same conctructor and it looks like this:
class MarshId extends StatefulWidget {
final int ttId;
const MarshId({Key key, this.ttId}) : super(key: key);
#override
_MarshIdState createState() => _MarshIdState();
}
So my qiestions is pretty dumb: What am I doing wrong? I did everything as docs say and I reserched here on SO, but it dosen't work and I copmpletly don't understand what I am doing wrong. I also used this expression as here reccomend but now it seems like it is not work:marshes = marshes.where((marsh) => marsh.ttId == ttId).toList(); I understand this task: I need to filter the data by category and when the user clicks on certain transport type, for example, "bus", he gets to the screen where there is only a list of bus numbers. But I can't figure out what exactly I am doing wrong when I write filtering.
I really appreciate your help, guys!
you can pass id to other page by using constructor in your ListBus() class the you simply use where like this
ie. let's say you parameter name is ttId;
then your filter will be
marshes = marshes.where((marsh) => marsh.ttId == ttId).toList();

How can i use the result from the first API call as input for the second API call?

I have to make multiple API calls in order to get the actual data. I have written the below code to make the first API call. It works but I have to use the return value (let'say it returns access token) from the first call, and use this access token as part of the header on the second API call. How can I achieve that?
class Service {
final String url;
Map<String, String> header = new Map();
Map<String, String> body = new Map();
Service(this.url, this.header, this.body);
Future<Data> postCall() async {
final response = await http.post(url, headers: header, body: body);
return Data.fromJson(json.decode(response.body));
}
}
class MyApp extends StatelessWidget {
Service service;
Service serviceTwo;
....
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: FutureBuilder<Data>(
future: service.postCall,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.accessToken);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);}}
There are many ways of achieving that, the simplest one is just using await on your method to append the future calls.
So your method postCall() would be something like this:
Future<Data> postCall() async {
// The first call, suppose you'll get the token
final responseToken = await http.post(url, headers: header, body: body);
// Decode it as you wish
final token = json.decode(responseToken.body);
// The second call to get data with the token
final response = await http.get(
url,
headers: {authorization: "Bearer $token"},
);
// Decode your data and return
return Data.fromJson(json.decode(response.body));
}
If it is a token you'll use many times, I recommend you to store it in flutter_secure_storage and use it as you wish.