swiftuiswiftui-listswiftui-navigationlinkdetailsviewobservableobject

Detail View from different Data Model (API response) in swiftUI


Elaborating the Problem in depth with code

I have a data model (API response) using which I am creating a list. The list items should also display their details in detail view. The problem is details of list items are coming from a different API other than the API used for creating the list. The id of one item is passed to API which in response provides the details of the item.

This is the data model(including items only problem specific):

struct TrackSample : Codable, Identifiable {
    let id = UUID()
    let success : Bool
    let message : String
    let trackResponse : [TrackResponse]

    enum CodingKeys: String, CodingKey{
    case success = "IsSuccess"
    case message = "Message"
    case trackResponse = "ResponseData"
    }}

struct TrackResponse : Codable, Identifiable {
    let id = UUID()
    let patientID : String
    let name : String
    let testCount : Int
//..some more//

enum CodingKeys: String, CodingKey {
    case patientID = "PatientId"
    case name = "Name"
    case status = "Status"
case testCount = "NoOfTests"
    }}

ViewModel to fetch API response ( TrackResource() is a different class which implements the networking call):

class TrackViewModel : ObservableObject
    {
        @Published var trackReport = [TrackResponse]()
        @Published var navigate:Bool = false
        //other var
        private let trackResource = TrackResource()

    func getTrack()
    {
    //code to get data based on which button is clicked in Options 
    screen. 
    There are five options to choose:
    if else statement follows//
        
    centerID = "100"
    
        let trackRequest = TrackRequest(CenterId:centerID, 
    SearchText:searchText, StartDate:startDate, EndDate:endDate)
     trackResource.track(trackRequest: trackRequest)
        {
         response in
         if(response?.success==true)
          {
                DispatchQueue.main.async {
                self.navigate = true
                self.trackReport = response?.trackResponse ?? []
            }
        }
        else
        {
            DispatchQueue.main.async {
                self.errorMessage = response?.message ?? "No Data"
             //   self.isPresentingAlert
            }
        }}}}

The view YdaySample which represents the list :

struct YdaySample: View {

@ObservedObject var tracking : TrackViewModel

    var body: some View {

    NavigationView{

        List{
         ForEach(tracking.trackReport)
            { truck in
             NavigationLink(destination: TrackDetail(track:truck))
                        {
                            YdayRow(truck: truck)
                        }
                }
         if(tracking.trackReport.isEmpty){
        Text("No Record Found !")
            //formatting
        }}}}}

struct YdaySample_Previews: PreviewProvider {
static var previews: some View {
 YdaySample(tracking: TrackViewModel())
         }}

The YdayRow() :

      struct YdayRow: View {
     var truck : TrackResponse
     var body: some View {
     VStack{
         HStack{
                Text(truck.name)
            //formatting//
        }
    HStack{
            Text("P.Id:")
            //formatting//
            Text(truck.patientID)
            //formatting//
    
        Spacer()
        Text("Total Test:")
            //formatting//
        Text("\(truck.testCount)")
            //formatting//
    }}}}

  struct YdayRow_Previews: PreviewProvider {
         static var previews: some View {
        YdayRow(truck: TrackResponse(patientID: "1", name: "test", 
  status: "null", crmNo: "null", 
      recordDate: "somedate", uniqueID: "null", testCount: 4))
        }
    }

TrackDetail() updated:

 struct TrackDetail: View {

 var track: TrackResponse
 @State var patientDetail: DetailResponse
 
 var body: some View {
        VStack{
            HStack{
                Text(track.name)
                   //formatting
                    }.frame(maxWidth: .infinity, alignment: .center)
            
                HStack{
                Text("P.Id:")
                        //formatting
                    Text(track.patientId)
                       //formatting
               
               }
           
                List{ForEach(patientDetail.detailResponse)
                        {detail in
                            HStack
                        { Text("Barcode: ")
                            Text(detail.barcode)
                        }
                    }
                }.onAppear{
                    Task{
                        do{
                            try await getDetail()
                        }catch{
                Alert(title: Text("Error"),message: Text("Not Found"), 
            dismissButton: .cancel())
                        }}}}}

func getDetail() async throws{
    
  
    var urlComponents = URLComponents()
    urlComponents.scheme = "http"
    urlComponents.host = "xx.xxx.xx.xx"
    urlComponents.path = "/api/reports/getalltests"
    urlComponents.queryItems = [URLQueryItem(name: "centerId", value: 
    ("\(668)")),URLQueryItem(name: "patientId", value: "\ 
    (track.patientId)")]

    let url = urlComponents.url

    var request = URLRequest(url: url!)
    print(request)
    request.setValue("application/json", forHTTPHeaderField: "Accept")
    request.setValue("application/json", forHTTPHeaderField: "Content- 
    Type")
    request.setValue("Basic xcvgjhalddjdj",forHTTPHeaderField: 
    "Authorization")
    // Send HTTP Request
    let task = URLSession.shared.dataTask(with: request) { (data, 
     response, error) in
        
        // Check if Error took place
        if let error = error {
            print("Error took place \(error)")
            return
        }
        
        // Read HTTP Response Status code
        if let response = response as? HTTPURLResponse {
            print("Response HTTP Status code: \(response.statusCode)")
        }
        
       if let data = data
        
        {
           
              let dataString = String(data:data,encoding: .utf8)
              print(dataString!)
            
             do{
                let json = try JSONDecoder().decode(DetailResponse.self, 
               from: data)
               print(json)
                DispatchQueue.main.async {
                    self.patientDetail = json
                }
             
    
            }catch{
                print("error \(error)")
            }
                      
        }
    };task.resume()
        }}



    struct TrackDetail_Previews: PreviewProvider {
    static var previews: some View {
    TrackDetail(track: TrackResponse(patientId: "4193716", name: "Dummy 
    Report HCV RNA", status: "null", crmNo: "null", recordDate: "2022- 
     04-15", uniqueId: "null", testCount: 10), patientDetail: 
     DetailResponse(success: false, message: "mess", detailResponse: 
       []))
    }}

print(request) is printing right URL as desired also the patientID is correct in url (http://xxx..x.x/api/reports/getalltests/centerId=668&patientId=(tapped id))

But it is throwing error in decoding saying "Authrization has been denied for this request" error keynotfound(codingkeys(stringvalue: "ResponseData", intvalue:nil), Swift.decodingerror.context(codingpath: [],debugdescription: "No value associated with key CodingKeys(stringvalue: "ResponseData", intvalue:nil)("ResponseData", underlyingError:nil)

struct DetailResponse : Codable{
    let success : Bool ?
    let message : String
    let detailResponse : [PatResponse]

    enum CodingKeys: String, CodingKey{
    case success = "IsSuccess"
    case message = "Message"
    case patResponse = "ResponseData"
    }}

struct PatResponse : Codable, Identifiable {    
    var barcode: String
    var:id String {barcode} 
    let testname : String?
    let packageName : String?
    let status: String?
    let sampleRecievedTime: String?
    let recordDate: String

enum CodingKeys: String, CodingKey {
    case packageName = "PackageName"
    case testname = "TestName"
    case status = "Status"
    case sampleRecievedTime = "SampleRecievedTime"
    case recordDate = "RecordDate"
    case barcode = "Barcode"
    }}

///////**************/////////////////// The detail view is showing name and ID as they are coming from TrackResponse but status and barcode are not as they are from different API.

When the user click/tap an item in list, the patientID and centerId is sent as query param in GET request which in response will provide the details associated with the given patientId (center ID is constant). How to implement this?


Solution

  • How about something like this?

    struct TrackDetail: View{
        
        var track: TrackResponse
        @State var details: TrackDetails?
        
        var body: some View{
            HStack{
                //Detail implementation
            }.onAppear{
                Task{
                    do{
                        try await doStuff()
                    }catch{
                        //Alert
                    }
                }
            }
        }
        
        func doStuff() async throws{
            // pull Details from Api
            
        }
    }