How to call an async method in initState in flutter - api

I need to get some information from an endpoint. I have this method:
List<Widget> cardsList = List();
List<dynamic> cardsId = List();
addToList() async {
var jsonData = await Provider.of<CardData>(context).cardsList;
print(jsonData);
for (var i = 0, len = jsonData.length; i < len; i++) {
if (jsonData[i]['account_type'] == "1") {
cardsList.add(
BankCard(
bankName: jsonData[i]['title'],
colors: [Color(0xFFD00E00), Color(0xFFF44336)],
cardNumber: jsonData[i]['number'],
cardDesc: jsonData[i]['description'],
),
);
cardsId.add(jsonData[i]['id']);
}
}
}
and a class as provider data called CardData:
import 'package:flutter/material.dart';
import '../cards/cards.dart';
class CardData extends ChangeNotifier {
static Cards cards = Cards();
Future<dynamic> cardsList = cards.getCards();
}
and a class called Card to send request and doing all other stuff:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class Cards {
String _accessToken;
String _refreshToken;
Future<dynamic> getCards() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
_accessToken = sharedPreferences.getString("access");
_refreshToken = sharedPreferences.getString("refresh");
var jsonData;
var response = await sendRequestToGetCards(
url: "http://10.0.2.2:8000/accounts/list/", accessToken: _accessToken);
if (response.statusCode == 200) {
jsonData = json.decode(utf8.decode(response.bodyBytes));
return jsonData;
} else if (response.statusCode == 401) {
_accessToken = await getNewAccessToken(_refreshToken);
response = await sendRequestToGetCards(
url: "http://10.0.2.2:8000/accounts/list/",
accessToken: _accessToken);
if (response.statusCode == 200) {
jsonData = json.decode(utf8.decode(response.bodyBytes));
return jsonData;
}
}
}
getNewAccessToken(String refreshToken) async {
var refreshResponse = await http.post(
"http://10.0.2.2:8000/users/api/token/refresh/",
body: {'refresh': refreshToken});
if (refreshResponse.statusCode == 200) {
var jsonData = json.decode(refreshResponse.body);
return jsonData['access'];
}
}
sendRequestToGetCards({String url, String accessToken}) async {
var response = await http.get(
url,
headers: {"Authorization": "Bearer $accessToken"},
);
return response;
}
}
But when I call addToList method in initState to retrieve data before build method, the main UI disappears.
What's wrong with it?

You can call async function in the initState, but as it itself is not an async function it will not wait for futures to complete before moving on to the build method, which is why your UI disappears because it is building with no data so there are no cards. I would suggest using a FutureBuilder in your build method to build when the async function returns.

Related

oauth2 in core 3.1

I am using core 3.1 to connect to the canvas API, this is part of my code..
services.AddAuthentication(config =>
{
config.DefaultAuthenticateScheme = "CanvasCookies";
config.DefaultSignInScheme = "CanvasCookies";
config.DefaultChallengeScheme = "CanvasLMS";
})
.AddCookie("CanvasCookies")
.AddOAuth("CanvasLMS", config =>
{
var canvas_domain = Configuration.GetValue<string>("Canvas:Domain");
var client_secret = Configuration.GetValue<string>("Canvas:Secret");
var client_id = Configuration.GetValue<string>("Canvas:Client_id");
config.ClientId = client_id;
config.ClientSecret = client_secret;
config.CallbackPath = new PathString("/oauth/callback");
//config.Scope.Add("google.com")
config.AuthorizationEndpoint = $"{canvas_domain}login/oauth2/auth";
config.TokenEndpoint = $"{canvas_domain}login/oauth2/token";
config.UserInformationEndpoint = $"{canvas_domain}api/v1/users//courses";
config.SaveTokens = true;
config.Events = new OAuthEvents()
{
OnCreatingTicket = context =>
{
var accessToken = context.AccessToken;
var base64payload = accessToken.Split('.')[1];
var bytes = Convert.FromBase64String(base64payload);
var jsonPayload = Encoding.UTF8.GetString(bytes);
var claims = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonPayload);
foreach(var claim in claims)
{
context.Identity.AddClaim(new Claim(claim.Key, claim.Value));
}
return Task.CompletedTask;
}
this is the controller
public class APICanvasController : Controller
{
...
[Authorize]
public async Task<IActionResult> Secret()
{
var serverResponse = await AccessTokenRefreshWrapper(
() => SecuredGetRequest("https://localhost:44388/secret/index"));
var apiResponse = await AccessTokenRefreshWrapper(
() => SecuredGetRequest("https://localhost:44388/secret/index"));
return View();
}
private async Task<HttpResponseMessage> SecuredGetRequest(string url)
{
var token = await HttpContext.GetTokenAsync("access_token");
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
return await client.GetAsync(url);
}
public async Task<HttpResponseMessage> AccessTokenRefreshWrapper(
Func<Task<HttpResponseMessage>> initialRequest)
{
var response = await initialRequest();
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await RefreshAccessToken();
response = await initialRequest();
}
return response;
}
private async Task RefreshAccessToken()
{
...
}
}
}
when I execute the code I obtain this error
Exception: The oauth state was missing or invalid.
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
Any idea what I am doing wrong?
Thanks
CallbackPath is not supposed to refer to a controller, it refers to a unique path handled by the auth middleware. It will redirect back to your controller when it's done.
"/oauth/callback" should handle oauth authentication result as a json instead of page.

How to setup a base URL and where do I declare it in flutter dio for api calls?

like how to fix boilerplate code in separate file and use it in ui pages.
I Need to declare this uri variable in separate file and access across over all pages:
static var uri = "https://xxx/xxx/web_api/public";
static BaseOptions options = BaseOptions(
baseUrl: uri,
responseType: ResponseType.plain,
connectTimeout: 30000,
receiveTimeout: 30000,
// ignore: missing_return
validateStatus: (code) {
if (code >= 200) {
return true;
}
}); static Dio dio = Dio(options);
In UI page i have to declare that uri variable and BaseOption variable in this future function:
Future<dynamic> _loginUser(String email, String password) async {
try {
Options options = Options(
headers: {"Content-Type": "application/json"},
);
Response response = await dio.post('/login',
data: {
"email": email,
"password": password,
"user_type": 2,
"status": 1
},
options: options);
if (response.statusCode == 200 || response.statusCode == 201) {
var responseJson = json.decode(response.data);
return responseJson;
} else if (response.statusCode == 401) {
throw Exception("Incorrect Email/Password");
} else
throw Exception('Authentication Error');
} on DioError catch (exception) {
if (exception == null ||
exception.toString().contains('SocketException')) {
throw Exception("Network Error");
} else if (exception.type == DioErrorType.RECEIVE_TIMEOUT ||
exception.type == DioErrorType.CONNECT_TIMEOUT) {
throw Exception(
"Could'nt connect, please ensure you have a stable network.");
} else {
return null;
}
}
}
You can create app_config.dart file and manage different environments like below:
const _baseUrl = "baseUrl";
enum Environment { dev, stage, prod }
Map<String, dynamic> _config;
void setEnvironment(Environment env) {
switch (env) {
case Environment.dev:
_config = devConstants;
break;
case Environment.stage:
_config = stageConstants;
break;
case Environment.prod:
_config = prodConstants;
break;
}
}
dynamic get apiBaseUrl {
return _config[_baseUrl];
}
Map<String, dynamic> devConstants = {
_baseUrl: "https://devapi.xyz.com/",
};
Map<String, dynamic> stageConstants = {
_baseUrl: "https://api.stage.com/",
};
Map<String, dynamic> prodConstants = {
_baseUrl: "https://api.production.com/",
};
Maybe instead of statically declaring your Dio object you could put it in a class, also put your loginUser function in there, and use Provider to obtain that object to call it where you need it.
class Api {
static var uri = "https://xxx/xxx/web_api/public";
static BaseOptions options = BaseOptions(
baseUrl: uri,
responseType: ResponseType.plain,
connectTimeout: 30000,
receiveTimeout: 30000,
// ignore: missing_return
validateStatus: (code) {
if (code >= 200) {
return true;
}
});
Dio dio = Dio(options);
Future<dynamic> loginUser(String email, String password) async {
try {
RequestOptions options = RequestOptions(
headers: {"Content-Type": "application/json"},
);
Response response = await dio.post('/login',
data: {
"email": email,
"password": password,
"user_type": 2,
"status": 1
},
options: options);
//the rest of your code here
}
https://pub.dev/packages/provider
Provider(
create: (_) => Api(),
child: ...
)
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
YourWidget(
child: Consumer<Api>(
builder: (context, api, child) {
return FutureBuilder<dynamic>(
future: api.loginUser('mail#mail.com', 'user_password')
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
//show a widget based on snapshot.data
} else {
//show another widget
}
}
},
),
)

Flutter - How to add Multipi API data source?

I have 2 API with the same data but different link, Sometime one of them isn't working so I want to try to get data from first one if it's not working so get data from the second (as a backup data source )
Can any one help me to do this ?
Thanks in advance.
import 'package:http/http.dart';
import 'dart:convert';
class MyWorking {
Future getData() async {
String data;
Response response = await get(
'first Url');
if (response.statusCode == 200) {
data = response.body;
print(data);
return jsonDecode(data);
} else {
print(response.statusCode);
}
}
}
I tried this and it worked but as you can see the second url in if statement so is their away to but it under the first url ?
import 'package:http/http.dart';
import 'dart:convert';
class MyWorking {
Future getData() async {
String data;
Response response = await get('first URL');
if (response.statusCode == 200) {
data = response.body;
return jsonDecode(data);
} else if (response.statusCode != 200) {
response = await get('second URL');
data = response.body;
return jsonDecode(data);
} else {
print(response.statusCode);
}
}
}
try this:
Future getData(int index) async { //changed this line
List<String> data = ['first url','second url']; //changed this line
Response response = await get(data[index]);
if (response.statusCode == 200) {
data = response.body;
print(data);
return jsonDecode(data);
} else {
print(response.statusCode);
index == 0 ? getData(1):getData(0); //add this line
}
}

Unable to access fetched data in initState in Flutter

I have class named Cards that has a method getCards that returns back a Future. I use this method to get cards from an endpoint.
Cards:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class Cards {
String _accessToken;
String _refreshToken;
List<dynamic> cardsId = List();
Future<dynamic> getCards() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
_accessToken = sharedPreferences.getString("access");
_refreshToken = sharedPreferences.getString("refresh");
var jsonData;
var response = await sendRequestToGetCards(
url: "http://10.0.2.2:8000/accounts/list/", accessToken: _accessToken);
if (response.statusCode == 200) {
jsonData = json.decode(utf8.decode(response.bodyBytes));
return jsonData;
} else if (response.statusCode == 401) {
_accessToken = await getNewAccessToken(_refreshToken);
response = await sendRequestToGetCards(
url: "http://10.0.2.2:8000/accounts/list/",
accessToken: _accessToken);
if (response.statusCode == 200) {
jsonData = json.decode(utf8.decode(response.bodyBytes));
return jsonData;
}
}
}
getNewAccessToken(String refreshToken) async {
var refreshResponse = await http.post(
"http://10.0.2.2:8000/users/api/token/refresh/",
body: {'refresh': refreshToken});
if (refreshResponse.statusCode == 200) {
var jsonData = json.decode(refreshResponse.body);
return jsonData['access'];
}
}
sendRequestToGetCards({String url, String accessToken}) async {
var response = await http.get(
url,
headers: {"Authorization": "Bearer $accessToken"},
);
return response;
}
}
I have an other class called CardData as my Provider data/state:
import 'package:flutter/material.dart';
import '../cards/cards.dart';
class CardData extends ChangeNotifier {
static Cards cards = Cards();
Future<dynamic> cardsList = cards.getCards;
}
Which you can see I created an object from Cards to access getCards that makes me able to access the returned Future and saving it in cardsList.
Now in my widget that I used to display all the cards I created a method called addToList to access the Provider data.
I've created some lists to save Widgets to pass them to other Widget later.
List<Widget> cardsList = List();
List<dynamic> cardsId = List();
List<Widget> nonCards = List();
List<dynamic> nonCardsId = List();
addToList() async {
var jsonData = await Provider.of<CardData>(context).cardsList;
for (var i = 0, len = jsonData.length; i < len; i++) {
if(jsonData[i]['account_type'] == "1") {
cardsList.add(
BankCard(
bankName: jsonData[i]['title'],
colors: [Color(0xFFD00E00), Color(0xFFF44336)],
cardNumber: jsonData[i]['number'],
cardDesc: jsonData[i]['description'],
),
);
cardsId.add(jsonData[i]['id']);
} else if(jsonData[i]['account_type'] == "2") {
nonCards.add(
NonBankCard(
bankName: jsonData[i]['title'],
colors: [Color(0xFFFF4B2B), Color(0xFFFDB76C)],
),
);
nonCardsId.add(jsonData[i]['id']);
}
}
}
But I need to use addToList method in initState as you know but I can't. when I do use it there the app screen will disappears.
you should initialize your list at the beginning
List<Widget> cardsList = new List<Widget>();
Call addToList inside your initState function:
#override
void initState() {
addToList();
}
at the end of addToList() put the updated list in setState()
setState(() {
cardsList = List.from(cardsList);
});

How to send data through API in Flutter?

When I try to login (email and password parameter) through APIs, data passes in row format, but I want to post data in form format.
Because of this problem, when I pass data it does not accept parameter value and shows null and gives error.
So, how can I solve this problem?
> This _loginUser() method In call on login button click.
_loginUser(context) async {
Map<String, dynamic> loginBodyResponse = {
"email": _emailController.text,
"password": _passswordController.text,
};
try {
setState(() {
_isLoading = true;
});
> APIs call
Map loginResponse = await NetworkHttp.postHttpMethod(
'${AppConstants.apiEndPoint}${AppConstants.loginApi}',
loginBodyResponse);
print('email:' + _emailController.text);
print('Password:' + _passswordController.text);
print(loginBodyResponse);
print('===================Login Response=========================');
print(loginResponse['body']);
print(loginResponse['body']['login']);
print("---------------------------");
if (loginResponse['body']['status'] != "success") {
setState(() {
_isLoading = false;
});
String errormessage = loginResponse['body']['msg'];
print("---------------------------");
print(errormessage);
ErrorDialog.showErrorDialog(context, errormessage, "Error");
} else {
print("Login Successfully");
print("============================================");
setState(() {
_isLoading = false;
});
NavigatorHelper.verifyOtpScreen(context);
}
} catch (e) {
setState(() {
_isLoading = false;
});
ErrorDialog.showErrorDialog(context, e.toString(), "Error");
print('error while login $e');
}
}
NetworkHttp class which I used in above mention code. when I try to login it shows null parameter in console
class NetworkHttp {
static Future<Map<String, String>> _getHeaders() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String token = prefs.getString('token');
if (token != null) {
return {
'Content-type': 'application/json',
'Accept': 'application/json',
'AccessToken': '$token',
};
} else {
return {
'Content-type': 'application/json',
'Accept': 'aapilcation/json',
};
}
}
static Future<Map<String, dynamic>> postHttpMethod(String url, body) async {
print('url');
print(url);
print('body');
print(body);
Map headers = await _getHeaders();
print('headers');
print(headers);
http.Response response = await http.post(
url,
headers: headers,
body: json.encode(body),
);
Map<String, dynamic> responseJson = {
'body': json.decode(response.body),
'headers': response.headers
};
return responseJson;
}
Just add headers with the json_data .
Future<login> requestLogin(String username, String password , String device_id) async {
Map<String, String> headers = {"Content-type": "application/x-www-form-urlencoded"};
var json_body = { 'email' : username , 'password': password ,'device_id': device_id};
if(token != null){
headers.addAll({"Authorization" : "Bearer "+token});
}
// make POST request
final response = await http.post(baseUrl+ "/login", headers: headers, body: json_body);
print(response.body);
if (response.statusCode == 200) {
// If server returns an OK response, parse the JSON.
// return Post.fromJson(json.decode(response.body));
print(response.body);
return login.fromJson(json.decode(response.body));
} else {
// If that response was not OK, throw an error.
throw Exception(response.body);
}
}
With your post request just pass your parameters like this
your post request should look something like below.
response = await http.post("your_api_url",
body:{"email" : "value",
"password" : "value"});