How to show parsed data with SwiftUI? - api

I'm trying to show in a view parsed data from an API using SwiftUI.
I'm fetching correctly the data using this code:
import UIKit
class APIController: ObservableObject {
func apiCall() {
// URL
let url = URL(string: "https://geek-jokes.p.rapidapi.com/api?format=json")
guard url != nil else {
print("Error creating url object")
return
}
// URL Rquest
var request = URLRequest(url: url!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
// Specify the header
let headers = [
"x-rapidapi-key": "d1363cbe66msh266920b6366eaacp1f87dfjsn050e3f8e58e2",
"x-rapidapi-host": "geek-jokes.p.rapidapi.com"
]
request.allHTTPHeaderFields = headers
// Specify the body
// let jsonObject = [
// "sign": "Aries"
// ] as [String:Any]
//
// do {
// let requestBody = try JSONSerialization.data(withJSONObject: jsonObject, options: .fragmentsAllowed)
//
// request.httpBody = requestBody
// }
// catch {
// print("Error creating the data object from json")
// }
// Set the request type
request.httpMethod = "GET"
// Get the URLSession
let session = URLSession.shared
// Create the data task
let dataTask = session.dataTask(with: request) { data, response, error in
// Check for errors
if error == nil && data != nil {
// Try to parse out the JSON data
let decoder = JSONDecoder()
do {
let jokesData = try decoder.decode(JokesData.self, from: data!)
print(jokesData)
}
catch {
print("Error in JSON parsing")
}
// Try to print out the JSON data
// do {
// let dictionary = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String:Any]
// print(dictionary)
//
// }
// catch {
// print("Error parsing response data")
// }
}
}
// Fire off the data task
dataTask.resume()
}
}
Then I'm not sure if the data is correctly imported in JokesData.swift
import foundation
struct JokesData: Codable {
var joke:String = ""
}
And when I try to load the data in JokesView.swift, nothing appear :(
import SwiftUI
struct JokesView: View {
var jokesData: JokesData
#StateObject var viewRouter: ViewRouter
var body: some View {
Text(jokesData.joke)
.foregroundColor(.black)
}
}
If someone has an idea of what I did wrong, it would be very helpful.

In APIController add a property
#Published var jokesData = JokesData()
Replace the do - catch block in the completion handler with
do {
let jokesData = try decoder.decode(JokesData.self, from: data!)
print(jokesData)
DispatchQueue.main.async {
self.jokesData = jokesData
}
}
catch {
print(error)
}
it assigns the received data to the publishing jokesData property.
In the view create a #StateObject of the controller, in the onAppear modifier load the data, the view will be updated automatically.
import SwiftUI
struct JokesView: View {
#StateObject var apiController = APIController()
var body: some View {
Text(apiController.jokesData.joke)
.foregroundColor(.black)
.onAppear() {
apiController.apiCall()
}
}
}

Related

Show message and download file after password verified - Razor pages

Before download the file, user need to enter the password. So I want to show the message if password is correct and in the same time start the download the file to the user.
public async Task<IActionResult> OnPostAsync()
{
var getFileUpload = await _context.FileUpload.FirstAsync(c => c.Guid == Guid && c.ExpiredOn.HasValue);
if (!ModelState.IsValid)
{
var message = string.Join(" | ", ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage));
return BadRequest(message);
}
if (DateTime.Today > getFileUpload.ExpiredOn.Value.AddDays(1))
{
Exception = "File already expired. Please ask administrator to share again";
return Page();
}
try
{
bool verified = BCrypt.Net.BCrypt.Verify(Password, getFileUpload.PasswordHash);
if (!verified)
{
Exception = "Password is wrong, please enter correct password";
return Page();
}
byte[] fileBytes = System.IO.File.ReadAllBytes(getFileUpload.Path);
var fileName = getFileUpload.FileName;
File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Success = true;
return Page();
}
catch
{
Exception = "Failed to download the data";
return Page();
}
}
I can see the message, but file cannot download.
But when I change return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);, file able to download but cannot not alert the message.
Any idea how I can fix this?
You can verify the password with ajax first, and get the file after success. This is the pagemodel code.
public async Task<IActionResult> OnPostAsync()
{
//other code
if (true)
{
return new JsonResult("success");
}
else
{
return BadRequest();
}
}
public IActionResult OnGetFileAsync()
{
//get file from header
StringValues filename;
Request.Headers.TryGetValue("filename", out filename);
var stream = System.IO.File.OpenRead("file path");
string fileExt = Path.GetExtension("1.png");
var provider = new FileExtensionContentTypeProvider();
var memi = provider.Mappings[fileExt];
return File(stream, memi, Path.GetFileName("filename"));
}
Ajax in the page.
function verify() {
$.ajax({
url: '/?handler',
method: 'post',
headers: {
RequestVerificationToken: $('input:hidden[name="__RequestVerificationToken"]').val()
},
success: function (data,status) {
fetch("/?handler=file", {
//Write the filename to be obtained into the http header
headers: {
'filename': data
}
}).then(res => res.blob().then(blob => {
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
var filename = res.headers.get('content-disposition').split(';')[1].split('=')[1]
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}));
//set successful message
},
error: function () {
console.log('e')
//set the error message in the page
}
})
}

Set TempData in ViewComponent

I want to set TempData in View Component so that i can check it in Login Action when there is error in View Component.
Here is my .ts file code
window.onload = function () {
fetch('../Controller/ActionName',
{
headers: {
RequestVerificationToken: (<HTMLInputElement>document.getElementById("Token")).value
}
})
.then((response) => {
if (response.status != 200) {
redirectToHomePage();
}
response.text().then((data) => {
document.getElementById("Id")!.innerHTML = data;
});
});
// Redirect to home page
function redirectToHomePage() {
var url = window.location.origin;
window.location.href = url + '/Login/Login/';
}
};
Below is the ViewComponent code
public IViewComponentResult TestViewComponent()
{
try
{
// Do code here
}
Catch(Exception ex){
TempData["Error"] = "Err msg";
return View(viewName)
}
return View(viewName);
}
TempData is set correctly in ViewComponent, but when response get back from .ts file from redirectToHomePage() function to Login/Login TempData will be null.
So how to get TempData in Login action
Thank you in advance
Here is a definition of ViewComponent.TempData,And in the link you can see the TempData only have get method,So you cannot pass the TempData to Login/Login.

How to call an async method in initState in flutter

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.

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);
});

Conversion to Alamofire 4 breaks Alamofire.Request extension

I had this little extension on Alamofire 3, it was used to get certain tokens and data from the default response.
public extension Alamofire.Request {
private func authorizationHandler(queue: dispatch_queue_t? = nil, completionHandler: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> Void) -> Self {
return response { (req, res, data, error) in
if let headers = res?.allHeaderFields {
if let id = headers["x-uid"] as? String {
oHTTPEnvironment.x_uid = id
}
if let authToken = headers["x-access-token"] as? String {
oHTTPEnvironment.currentMutableToken = oHTTPEnvironment.nextMutableToken
oHTTPEnvironment.nextMutableToken = authToken
}
}
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(req!, res, data, error)
})
}
}
}
And now in Alamofire 4 and Swift 3 it get's an error on the "return response { (req, res, data, error) in"
The error is:
Cannot call value of non-function type 'HTTPURLResponse'
I even tried to do:
private func authorizationHandler(queue: DispatchQueue? = nil, completionHandler: (NSURLRequest, HTTPURLResponse?, NSData?, NSError?) -> Void) -> Self {
return response { resp in
debugPrint(resp)
}
}
But it gives the same error.