algolia / instantsearch-ios

⚡️ A library of widgets and helpers to build instant-search applications on iOS.
https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/ios/
Apache License 2.0
595 stars 58 forks source link

Add the possibility to map the index data model to a custom data model when fetching hits #264

Closed NicFontana closed 1 year ago

NicFontana commented 1 year ago

Is your feature request related to a problem? Please describe 🙏 I am working on a SwiftUI app. Let's consider a simple situation where I need to show a list of search results after the user provided a search text. I am using the HitsList component to display the results.

My index data model doesn't fit my view's data model, for instance:

Describe the solution you'd like 🤔 I would like to have the possibility to provide a mapping closure that maps my index data model to a custom data model, so when the fetch happens the items inside HitsPage can be transformed and stored following my custom data model definition.

Describe alternatives you've considered ✨ I have considered a couple of alternatives:

  1. Apply the mapping closure inside my view for each hits Although it should works, the closure would be executed each the view re-render, so it's not the best solution considering the performance.

  2. Provide a custom JSON decoder that does all the transformations needed Maybe this is the best solution at the moment, but it's not very friendly as a lot of boilerplate code is required.

Have ever considered this? Do you have any suggestions?

Thanks a lot, Niccolò

VladislavFitz commented 1 year ago

Hi @NicFontana ,

Thank you for your feedback.

I suppose, you are using the Hits component. The business logic of hits management is handled in the HitsInteractor component. To use it, you might define the Record object which might conform to Codable protocol.

Let's break down the cases that seem problematic to you.

Images URLs: image URLs are stored in my index as strings, so in my hit's struct I do have String instances. What if I want to work with a URL property instead?

The URL type conforms to Codable protocol. So, if you define a property of URL type in your Codable structure, it will be seamlessly decoded, if the provided string is a valid URL. This also works for an array of urls since, an Array of Codable values conforms to Codable protocol automatically.

struct Model: Codable {
  let imageURLs: [URL]
} 

Merged properties: my index data model has two (or more) properties that I want to merge in a single one, e.g. let city: String and let region: String that I want to merge in a single let location: String

The easiest way to achieve it, is to introduce a computed variable to your data Model:

struct Model: Codable {
  let city: String
  let region: String

  var location: String {
    "\(city), \(region)"
  } 
}

If you don't want to recalculate the location on each read access, you can declare it as a lazy variable:

struct Model: Codable {
  let city: String
  let region: String

  lazy var location: String  = {
    "\(city), \(region)"
  }()
}

By doing this, you will avoid the decoding boilerplate.

For more complex cases you can compose nested data models:

struct RawModel: Codable {
  let city: String
  let region: String
}

struct Model: Codable {
   let location: String

    init(from decoder: Decoder) throws {
      let rawModel  = RawModel(from: decoder)
      self.location = "\(rawModel.city), \(rawModel.region)"
    }
}

let hitsInteractor: HitsInteractor<Model> = ...

As you can see, the power of Swift allows you to introduce the transformations you need without additional layer of complexity on the InstantSearch side. I hope it answers your question. If you have other use-cases for which these approaches don't work, please share them, I'd be happy to help.

NicFontana commented 1 year ago

Hello @VladislavFitz

thanks for your suggestions, I really appreciated. I've been able to solve the issue by leveraging the init(from decoder: Decoder) throws you mentioned in the last example.

I close the issue.