I am having a problem to fetch and display a single item in Flutter from API - flutter

I have an endpoint that returns data in the following JSON format.
{
"data": [
{
"balance": "40.000000"
}
]
}
I want to display the above balance into a wallet UI. I am having difficulty to display a single element that is not using controller formfield.
Below is additional code:
wallet_service.dart
Future<ApiResponse> getWalletBalance() async {
ApiResponse apiResponse = ApiResponse();
try {
String token = await getToken();
final response = await http.get(Uri.parse(wallet),
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
switch(response.statusCode){
case 200:
apiResponse.data = Wallet.fromJson(jsonDecode(response.body));
break;
case 401:
apiResponse.error = unauthorized;
break;
default:
apiResponse.error = somethingWentWrong;
break;
}
}
catch (e){
apiResponse.error = serverError;
}
return apiResponse;
}
walletui.dart
class _DashboardState extends State<Dashboard> {
Wallet? wallet;
bool loading = true;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
//TextEditingController txtBalanceController = TextEditingController();
//int mybalance=0;
// get user detail
void getUserWallet() async {
ApiResponse response = await getWalletBalance();
if(response.error == null) {
setState(() {
wallet = response.data as Wallet;
//log('data: $wallet');
loading = false;
//txtBalanceController.text = wallet?.balance?? '';
});
}
else if(response.error == unauthorized){
logout().then((value) => {
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context)=>Login()), (route) => false)
});
}
else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('${response.error}')
));
}
}
#override
void initState() {
getUserWallet();
super.initState();
}
}
Wallet Model Class
class Wallet {
int? id;
String? balance;
Wallet({
this.id,
this.balance,
});
factory Wallet.fromJson(Map<String, dynamic> json){
return Wallet(
id: json['id'],
balance: json['balance'],
);
}
}
My goal is to store the balance in a variable wallet that I can execute in the UI and display that value.

Please follow this link call for api:https://github.com/JayswalViraj/Flutter-Login-With-Rest-API
Please you understand after implemente.

Related

Unhandled Exception: type 'List<dynamic>'

I'm trying to recieve a list from a Sql Api. The catch is that i need to give an id with the query. the Widget.klant.klantId has the value i need. i know it has somthing to do with the as List<Machine> in accountpage.dart. Hope you can help me with this problem. thanks in advance.
The hole error:
accountpage.dart:
class Accountpage extends StatefulWidget {
const Accountpage(this.klant);
final Klant klant;
#override
_AccountpageState createState() => _AccountpageState();
}
class _AccountpageState extends State<Accountpage> {
_AccountpageState();
final ApiService api = ApiService();
late List<Machine> machineList;
#override initState(){
super.initState();
_getMachine();
machineList = [];
}
void _getMachine() async{
machineList = (await ApiService().getMoreMachine(widget.klant.klantId.toString())) as List<Machine>;
Future.delayed(const Duration(seconds: 1)).then((value) => setState(() {}));
}
#override
Widget build(BuildContext context) {
//Here starts the body
api_machine.dart:
Future<Machine> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return Machine.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load a case');
}
}
MachineModel.dart:
List<Machine> welcomeFromJson(String str) => List<Machine>.from(json.decode(str).map((x) => Machine.fromJson(x)));
String welcomeToJson(List<Machine> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Machine {
Machine({
this.serienummerId,
this.serienummer,
this.bouwjaar,
this.urenstand,
this.locatie,
this.klantId,
});
int? serienummerId;
String? serienummer;
String? bouwjaar;
String? urenstand;
String? locatie;
String? klantId;
factory Machine.fromJson(Map<String, dynamic> json) => Machine(
serienummerId: json["SerienummerId"],
serienummer: json["Serienummer"],
bouwjaar: json["Bouwjaar"],
urenstand: json["Urenstand"],
locatie: json["Locatie"],
klantId: json["KlantId"],
);
Map<String, dynamic> toJson() => {
"SerienummerId": serienummerId,
"Serienummer": serienummer,
"Bouwjaar": bouwjaar,
"Urenstand": urenstand,
"Locatie": locatie,
"KlantId": klantId,
};
}
json result
[
{
"SerienummerId": 1,
"Serienummer": "-----",
"Bouwjaar": "2020",
"Urenstand": "10",
"Locatie": "---",
"KlantId": "1"
},
{
"SerienummerId": 2,
"Serienummer": "-----",
"Bouwjaar": "1998",
"Urenstand": "5010",
"Locatie": "----",
"KlantId": "1"
}
]
You are parsing the result as if it's a single Machine while it in fact is a list of machines. Process it as a list and also use the correct return type accordingly. Like
Future<List<Machine>> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return List<Machine>.from(json.decode(response.body).map((x) => Machine.fromJson(x)));
} else {
throw Exception('Failed to load a case');
}
}
the return type of the method is Machine:
Future<Machine> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return Machine.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load a case');
}
}
and then you cast a Machine to List :
machineList = (await ApiService()
.getMoreMachine(widget.klant.klantId.toString())) as List<Machine>;
I don't know what the JSON looks like... but if there is only one machine you could for example add it to a list like this:
machineList.add((await ApiService()
.getMoreMachine(widget.klant.klantId.toString())));
Update
Try this:
Future<List<Machine>> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
final jsonMachines = Machine.fromJson(json.decode(response.body));
return jsonMachines.map((item) => Machine.fromJson(item)).toList();
} else {
throw Exception('Failed to load a case');
}
}
I think this is because of in getMoreMachine you used return type as Machine actually you are assigning that value as List so make that change like this :
Future<List<Machine>> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return welcomeFromJson(response.body);
} else {
throw Exception('Failed to load a case');
}
}
might be other think is you can check your API response that is not returning List of machines.

" _CastError (Null check operator used on a null value) " Flutter

When interacting with my function that initiate specs, flutter return me this error : "_CastError (Null check operator used on a null value)"
error code :
initSpecsTechs() async {
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
Full code of my page setting (first line contain the error code):
import 'package:myapp/api_bike.dart';
import 'package:myapp/api_user.dart';
import 'package:myapp/ux_components.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'blur_filter.dart';
class SettingsPage extends StatefulWidget {
#override
SettingsPageState createState() => SettingsPageState();
SettingsPage({required Key key}) : super(key: key);
}
class SettingsPageState extends State<SettingsPage>
with AutomaticKeepAliveClientMixin<SettingsPage> {
User? _user;
List<Bike> _bikes = [];
Bike? favoriteBike;
Specs? specs;
bool connectedByBluetooth = true;
double _currentSliderAutomaticExtinctionValue = 0;
double _currentSliderAutomaticLockingValue = 0;
bool theftAlertIsActive = true;
bool batteryAlertIsActive = true;
bool maintainAlertIsActive = true;
bool constantBackLight = true;
double spaceAfterSmallTitle = 15;
double spaceAfterWidget = 20;
//INIT FUNCTIONS
init() async {
await initUser();
await initBikes();
await initFavoriteBike();
await initSpecsTechs();
}
Future initUser() async {
final storage = new FlutterSecureStorage();
String mail = await storage.read(key: "mail") ?? "";
String password = await storage.read(key: "password") ?? "";
UserResponse userResponse = await APIUser()
.login(mail, password); //L'identifiant peut ĂȘtre le username ou le mail
User? user = userResponse.user;
if (user != null) {
setState(() {
_user = user;
});
}
return user;
}
initBikes() async {
if (_user != null) {
List<Bike> bikes = await APIBike().getAllUserBikes(jwt: _user!.jwt);
setState(() {
_bikes = bikes;
});
} else {
print("could not get the bikes call the user is null");
}
}
initFavoriteBike() async {
if (_bikes.length == 0) {
favoriteBike = null;
} else if (_bikes.length == 1) {
setState(() {
favoriteBike = _bikes.first;
});
} else {
Bike? favBike = await APIBike().getFavoriteBike(jwt: _user!.jwt);
if (favBike != null) {
setState(() {
favoriteBike = favBike;
});
} else {
print("PROBLEM : FAVORITE BIKE IS NULL");
}
}
}
initSpecsTechs() async {
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
initBackLightMode() {
if (specs != null) {
bool constantBackLightValue = false;
if (specs!.rearLight == "fixed") {
constantBackLightValue = true;
}
setState(() {
constantBackLight = constantBackLightValue;
});
} else {
print("Fake value used for initBackLightMode");
}
}
initTheftAlertIsActive() {
if (specs != null) {
setState(() {
theftAlertIsActive = specs!.theftAlarm;
});
} else {
print("Fake value used for initStealAlertIsActive");
}
}
initBatteryAlertIsActive() {
if (specs != null) {
setState(() {
batteryAlertIsActive = specs!.batteryAlarm;
});
} else {
print("Fake value used for initBatteryAlertIsActive");
}
}
initMaintenanceAlertIsActive() {
if (specs != null) {
setState(() {
maintainAlertIsActive = specs!.maintenanceAlarm;
});
} else {
print("Fake value used for initMaintenanceAlertIsActive");
}
}
initBluetoothConnection() {
//If this value is false then the page is all grey with nothing active
setState(() {
connectedByBluetooth = true;
});
}
initSliderAutomaticExtinctionValue() {
if (specs != null) {
double sliderValue = 0;
if (specs!.automaticSwitchOff == 0) {
sliderValue = 4;
} else if (specs!.automaticSwitchOff == 5) {
sliderValue = 0;
} else if (specs!.automaticSwitchOff == 10) {
sliderValue = 1;
} else if (specs!.automaticSwitchOff == 15) {
sliderValue = 2;
} else if (specs!.automaticSwitchOff == 20) {
sliderValue = 3;
} else {
//If there is a problem (it is not suppose to happen, we set it to never)
sliderValue = 0;
}
setState(() {
_currentSliderAutomaticExtinctionValue = sliderValue;
});
} else {
print("Fake value used for initSliderAutomaticExtinctionValue");
}
}
initSliderAutomaticLockingValue() {
if (specs != null) {
double sliderValue = 0;
if (specs!.automaticLocking == 0) {
sliderValue = 4;
} else if (specs!.automaticLocking == 1) {
sliderValue = 0;
} else if (specs!.automaticLocking == 3) {
sliderValue = 1;
} else if (specs!.automaticLocking == 5) {
sliderValue = 2;
} else if (specs!.automaticLocking == 10) {
sliderValue = 3;
} else {
//If there is a problem (it is not suppose to happen, we set it to never)
sliderValue = 0;
}
setState(() {
_currentSliderAutomaticLockingValue = sliderValue;
});
} else {
print("Fake value used for initSliderAutomaticLockingValue");
}
}
#override
void initState() {
super.initState();
init();
}
//UPDATE FUNCTIONS
updateRearLightValue(String newValue) async {
//TODO change the value on the bike by BLE
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "rear_light",
newSpecValue: newValue);
if (operationSucceed) {
print("Rear light value updated successfully");
} else {
print("Error: rear light value didn't update correctly");
}
return operationSucceed;
}
updateAutomaticShutDownValue(double sliderValue) async {
//TODO change the value on the bike by BLE
int newValue = 0; // is also the value for never
if (sliderValue == 0) {
newValue = 5;
} else if (sliderValue == 1) {
newValue = 10;
} else if (sliderValue == 2) {
newValue = 15;
} else if (sliderValue == 3) {
newValue = 20;
} //else the new value is 0 which is never
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "automatic_switch_off",
newSpecValue: newValue);
if (operationSucceed) {
print("Automatic switch off value updated successfully");
} else {
print("Error: Automatic switch off value didn't update correctly");
}
return operationSucceed;
}
updateAutomaticLockingValue(double sliderValue) async {
//TODO change the value on the bike by BLE
int newValue = 0; // is also the value for never
if (sliderValue == 0) {
newValue = 1;
} else if (sliderValue == 1) {
newValue = 3;
} else if (sliderValue == 2) {
newValue = 5;
} else if (sliderValue == 3) {
newValue = 10;
} //else the new value is 0 which is never
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "automatic_locking",
newSpecValue: newValue);
if (operationSucceed) {
print("Automatic locking value updated successfully");
} else {
print("Error: Automatic locking value didn't update correctly");
}
return operationSucceed;
}
updateTheftAlertIsActive(bool newValue) async {
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "theft_alarm",
newSpecValue: newValue);
if (operationSucceed) {
print("Theft alert value updated successfully");
} else {
print("Error: theft alert value didn't update correctly");
}
return operationSucceed;
}
updateBatteryAlertIsActive(bool newValue) async {
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "battery_alarm",
newSpecValue: newValue);
if (operationSucceed) {
print("Battery alert value updated successfully");
} else {
print("Error: battery alert value didn't update correctly");
}
return operationSucceed;
}
updateMaintenanceAlertIsActive(bool newValue) async {
bool operationSucceed = await APIBike().updateSpecsTechs(
jwt: _user!.jwt,
bikeId: favoriteBike!.id,
specToUpdate: "maintenance_alarm",
newSpecValue: newValue);
if (operationSucceed) {
print("Maintenance alert value updated successfully");
} else {
print("Error: maintenance alert value didn't update correctly");
}
return operationSucceed;
}
/.../
And finally my api for the user :
import 'dart:collection';
import 'package:myapp/api_bike.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class ResponseWithError {
final bool result;
final String error;
ResponseWithError(this.result, this.error);
}
class APIUser {
//For dev
final String serverAddress =
"https://myadress.com/";
//TODO For production:
//final String serverAddress = "https://prodadress.com";
Future<ResponseWithError> createUser(
String firstname, String lastname, String email, String password) async {
var body =
"""{"username": "$firstname $lastname", "firstname": "$firstname", "lastname": "$lastname", "email": "$email", "password": "$password"}""";
var client = new http.Client();
var response = await client.post(
Uri.parse("$serverAddress/auth/local/register"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
print("USER CREATED");
print(response.body);
return ResponseWithError(true, "none");
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
var responseBody = jsonDecode(response.body);
if (responseBody["message"][0]["messages"][0]["id"] != null) {
String errorMessageId = responseBody["message"][0]["messages"][0]["id"];
if (errorMessageId == "Auth.form.error.email.format") {
return ResponseWithError(false, "email format");
}
}
return ResponseWithError(false, "not known");
}
}
Future<UserResponse> login(String identifier, String password) async {
var body = """{"identifier": "$identifier", "password": "$password"}""";
var client = new http.Client();
var response = await client.post(Uri.parse("$serverAddress/auth/local"),
body: body, headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
//print("USER LOGGED IN");
final parsed = jsonDecode(response.body);
User user = User.fromJson(parsed);
UserResponse userResponse = UserResponse(user, true);
return userResponse;
} else {
//print("Error " + response.statusCode.toString());
//print("Response Body");
//print(response.body);
var responseBody = jsonDecode(response.body);
if (responseBody["message"][0]["messages"][0]["id"] != null) {
String errorMessageId = responseBody["message"][0]["messages"][0]["id"];
if (errorMessageId == "Auth.form.error.confirmed") {
print("User not confirmed");
UserResponse userResponse = UserResponse(null, false);
return userResponse;
} else /*if(errorMessageId == "Auth.form.error.invalid")*/ {
print("email or password incorrect");
UserResponse userResponse = UserResponse(null, true);
return userResponse;
}
}
//Should not happen, but just in case
UserResponse userResponse = UserResponse(null, true);
return userResponse;
}
}
Future<bool> updateToken(String jwt, String notificationId) async {
var body = """{"notification_id": "$notificationId"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/token"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("NOTIFICATION TOKEN UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> updateEmail(String jwt, String newEmail) async {
var confirmed = false;
var body = """{"email": "$newEmail", "confirmed": "$confirmed"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/me"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("EMAIL UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> updateNames(
String jwt, String newFirstname, String newLastName) async {
var body =
"""{"username": "$newFirstname $newLastName", "firstname": "$newFirstname", "lastname": "$newLastName"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/me"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("NAMES UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> updatePassword(String jwt, String newPassword) async {
var body = """{"password": "$newPassword"}""";
var client = new http.Client();
var response = await client.put(Uri.parse("$serverAddress/users/me"),
body: body,
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer $jwt'
});
if (response.statusCode == 200) {
print("PASSWORD UPDATED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> sendConfirmationCode(String email) async {
var body = """{"email": "$email"}""";
var client = new http.Client();
var response = await client.post(
Uri.parse("$serverAddress/auth/send-email-confirmation"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
print("EMAIL SENT to $email");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> verifyEmail(String email, String code) async {
var body = """{"email": "$email", "confirmation": "$code"}""";
var client = new http.Client();
var response = await client.put(
Uri.parse("$serverAddress/auth/email-confirmation"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
print("EMAIL VERIFIED");
return true;
} else {
print("Error " + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
Future<bool> passwordForgotten({required String email}) async {
var body = """{"email": "$email"}""";
var client = new http.Client();
var response = await client.post(
Uri.parse("$serverAddress/auth/forgot-password"),
body: body,
headers: {"Content-Type": 'application/json'});
if (response.statusCode == 200) {
return true;
} else {
print("Error passwordForgotten :" + response.statusCode.toString());
print("Response Body");
print(response.body);
return false;
}
}
}
class UserResponse {
User? user;
bool userConfirmed;
UserResponse(this.user, this.userConfirmed);
}
class User {
final int id;
final String jwt;
final String username;
final String firstname;
final String lastname;
final String email;
final String provider;
final bool confirmed;
final bool? blocked;
final String? notificationId;
final String createdAt;
final String updatedAt;
final List<int>?
bikesId; //Here we just save the ids of the bikes linked to the user
final Subscription? subscription;
final Role role;
User({
required this.id,
required this.jwt,
required this.username,
required this.firstname,
required this.lastname,
required this.email,
required this.provider,
required this.confirmed,
required this.blocked,
required this.notificationId,
required this.createdAt,
required this.updatedAt,
required this.bikesId,
required this.subscription,
required this.role,
});
factory User.fromJson(Map<String, dynamic> json) {
if (json['user']['subscription'] != null) {
return User(
id: json['user']['id'] as int,
jwt: json['jwt'] as String,
username: json['user']['username'] as String,
firstname: json['user']['firstname'] as String,
lastname: json['user']['lastname'] as String,
email: json['user']['email'] as String,
provider: json['user']['provider'] as String,
confirmed: json['user']['confirmed'] as bool,
blocked: json['user']['blocked'] as bool?,
notificationId: json['user']['notification_id'] as String?,
createdAt: json['user']['created_at'] as String,
updatedAt: json['user']['updated_at'] as String,
bikesId: createBikesIdList(json['user']['bikes_id']),
subscription: Subscription(
id: json['user']['subscription']['id'],
name: json['user']['subscription']['name'],
createdAt: json['user']['subscription']['created_at'],
updatedAt: json['user']['subscription']['updated_at']),
role: Role(
id: json['user']['role']['id'],
name: json['user']['role']['name'],
description: json['user']['role']['description'],
type: json['user']['role']['type'],
));
} else {
return User(
id: json['user']['id'] as int,
jwt: json['jwt'] as String,
username: json['user']['username'] as String,
firstname: json['user']['firstname'] as String,
lastname: json['user']['lastname'] as String,
email: json['user']['email'] as String,
provider: json['user']['provider'] as String,
confirmed: json['user']['confirmed'] as bool,
blocked: json['user']['blocked'] as bool?,
notificationId: json['user']['notification_id'] as String?,
createdAt: json['user']['created_at'] as String,
updatedAt: json['user']['updated_at'] as String,
bikesId: createBikesIdList(json['user']['bikes_id']),
subscription: null,
role: Role(
id: json['user']['role']['id'],
name: json['user']['role']['name'],
description: json['user']['role']['description'],
type: json['user']['role']['type'],
));
}
}
describe() {
print("id : $id\njwt : $jwt");
}
}
class Role {
final int id;
final String name;
final String description;
final String type;
Role({
required this.id,
required this.name,
required this.description,
required this.type,
});
}
class Subscription {
final int id;
final String name;
final String createdAt;
final String updatedAt;
Subscription(
{required this.id,
required this.name,
required this.createdAt,
required this.updatedAt});
}
Can you try like this? I think user or favoriteBike is null. You need to null check.
initSpecsTechs() async {
if(_user != null && favoriteBike != null){
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
}
initSpecsTechs() async {
specs = await APIBike()
.getSpecsTechs(jwt: _user!.jwt, bikeId: favoriteBike!.id);
initBluetoothConnection();
initSliderAutomaticExtinctionValue();
initSliderAutomaticLockingValue();
initBackLightMode();
}
The "!" operator causes the exception instead use "?" like this
specs = await APIBike()
.getSpecsTechs(jwt: _user?.jwt??'', bikeId: favoriteBike?.id??'');

How do you turn a List <dynamic> into a List <Map> >>

I started to learn Flutter because I want to build an app which can handle API-Calls.
But now I'm frustrated because I want to make an infinite Load and don't get it to work.
The Problem is, that the method require an Future<List> but I dont know how to convert the response from the API into an List
Future<List<Map>> _getServerData(int length) async{
String api = data.url +length.toString();
final res=
await http.get("data.url");
if (res.statusCode == 200) {
List<dynamic> resp = jsonDecode(res.body);
return resp;
} else {
throw Exception('Failed to load DATA');
}
}
The whole class is out of an Tutorial from oodavid.
But in his tutorial he dont use an API
Future<List<Map>> _getExampleServerData(int length) {
return Future.delayed(Duration(seconds: 1), () {
return List<Map>.generate(length, (int index) {
return {
"body": WordPair.random().asPascalCase,
"avatar": 'https://api.adorable.io/avatars/60/${WordPair.random().asPascalCase}.png',
};
});
});
}
That was the how he solved it
Down below is the whole class
import 'dart:async';
import 'dart:convert';
import 'package:Kontra/pages/articel_list.dart';
import 'package:http/http.dart' as http;
import 'package:Kontra/api/url.dart' as data;
import 'package:Kontra/api/articelsResponse.dart';
/// Example data as it might be returned by an external service
/// ...this is often a `Map` representing `JSON` or a `FireStore` document
Future<List<Map>> _getServerData(int length) async{
String api = data.url +length.toString();
final res=
await http.get(data.url);
if (res.statusCode == 200) {
List<dynamic> resp = jsonDecode(res.body);
return resp;
} else {
throw Exception('Failed to load DATA');
}
}
/// PostModel has a constructor that can handle the `Map` data
/// ...from the server.
class PostModel {
String sId;
String title;
String text;
String author;
String pictures;
String link;
int postId;
String createdAt;
PostModel({this.title, this.text, this.pictures, this.link, this.postId});
factory PostModel.fromServerMap(Map<String, dynamic> json) {
return PostModel(
title: json['title'],
text: json['text'],
pictures: json['pictures'],
link: json['link'],
postId: json['postId']
);
}
}
/// PostsModel controls a `Stream` of posts and handles
/// ...refreshing data and loading more posts
class PostsModel {
int reload = 0;
Stream<List<PostModel>> stream;
bool hasMore;
bool _isLoading;
List<Map> _data;
StreamController<List<Map>> _controller;
PostsModel() {
_data = List<Map>();
_controller = StreamController<List<Map>>.broadcast();
_isLoading = false;
stream = _controller.stream.map((List<Map> postsData) {
return postsData.map((Map postData) {
return PostModel.fromServerMap(postData);
}).toList();
});
hasMore = true;
refresh();
}
Future<void> refresh() {
return loadMore(clearCachedData: true);
}
Future<void> loadMore({bool clearCachedData = false}) {
if (clearCachedData) {
_data = List<Map>();
hasMore = true;
}
if (_isLoading || !hasMore) {
return Future.value();
}
_isLoading = true;
return _getServerData(reload++).then((postsData) {
_isLoading = false;
_data.addAll(postsData);
hasMore = (_data.length < 30);
_controller.add(_data);
});
}
}
Thanks for your help guys
Try with
return List<Map>.from(resp.whereType<Map>());
Or
return resp.whereType<Map>().toList();
Or
return resp.cast<Map>();

Flutter WebRTC audio but no video

So I'm building a video calling application using flutter, flutterWeb, and the WebRTC package.
I have a spring boot server sitting in the middle to pass the messages between the two clients.
Each side shows the local video, but neither shows the remote. Audio does work though. I got som nasty feedback loops. Testing with headphones showed that audio does indeed work.
My singaling code
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
MediaStream _localStream;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating) {
return;
}
isNegotiating = true;
var description = answer.data['description'];
if (_peerConnection == null) {
return;
}
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
_localStream = await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.addStream(_localStream);
_peerConnection.onSignalingState = (state) {
//isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth': '640',
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
Here are my widgets
class _CallScreenState extends State<CallScreen> {
StompClient _client;
CallingService _callingService;
RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
final UserService userService = GetIt.instance.get<UserService>();
void onConnectCallback(StompClient client, StompFrame connectFrame) async {
var currentUser = await userService.getCurrentUser();
_callingService = CallingService(
_client,
widget.intent.toUserId.toString(),
currentUser.id.toString(),
widget.intent.authToken,
onAddRemoteStream,
onRemoveRemoteStream,
onAddLocalStream);
if (widget.intent.initialMessage != null) {
_callingService.processMessage(jsonDecode(widget.intent.initialMessage));
} else {
_callingService.startCall();
}
}
void onAddRemoteStream(MediaStream stream) {
_remoteRenderer.srcObject = stream;
}
void onRemoveRemoteStream(MediaStream steam) {
_remoteRenderer.srcObject = null;
}
void onAddLocalStream(MediaStream stream) {
_localRenderer.srcObject = stream;
}
#override
void initState() {
super.initState();
_localRenderer.initialize();
_remoteRenderer.initialize();
_client = StompClient(
config: StompConfig(
url: 'ws://${DomainService.getDomainBase()}/stomp',
onConnect: onConnectCallback,
onWebSocketError: (dynamic error) => print(error.toString()),
stompConnectHeaders: {'Authorization': "${widget.intent.authToken}"},
onDisconnect: (message) => print("disconnected ${message.body}"),),
);
_client.activate();
}
#override
Widget build(BuildContext context) {
return PlatformScaffold(
pageTitle: "",
child: Flex(
direction: Axis.vertical,
children: [
Flexible(
flex: 1,
child: RTCVideoView(_localRenderer),
),
Flexible(
flex: 1,
child: RTCVideoView(_remoteRenderer),
)
],
),
);
}
}
I put a print statment in the the widget on the addRemoteStream callback, and it's getting called. So some kind of stream is being sent. I'm just not sure why the video isnt' showing.
So my problem was that I wasn't adding queued candidates to the caller.
I added
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
to the processAnswer method and it works just fine!

Flutter WebRTC Cant connect to peers. Failed to set remote answer sdp: Called in wrong state: kStable

So I'm using flutter (And flutter for web) to build a WebRTC client. I have a spring-boot server acting as the go-between for two clients. They both subscribe to a WebSocket to get messages from the other. It does nothing more than that.
I'm getting Error: InvalidStateError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable
I don't know why this error is happening.
Here's the code for the signalling
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating){
return;
}
var description = answer.data['description'];
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.onSignalingState = (state) {
isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddTrack = (MediaStream stream, _) {
this.onAddRemoteStream(stream);
print("sending stream from track");
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
print("sending stream");
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
print("sending candidate");
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{"url" : "stun:stun2.1.google.com:19302"},
{'url' : 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth':
'640', // Provide your own width, height and frame rate here
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
My first problem was I was not setting the local description for the offer/answer stage.
However, when I add a new stun server, I get the same exception. Either way, I don't get a remote stream showing.
So when I was creating the offer and answer I wasn't setting local description. So there's that.
It's still not showing remote connections though.