In building a teaching app against the set of APIs in the New York Times public portal, it was necessary to write three different, buy very similar sets of Kotlin data classes into which the REST data would be parsed. Each endpoint nominally returned a list of articles, but their field names and class hierarchy was subtly different.

Example: The Article Search endpoint versus the Top Stories endpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
//  Here are the data classes for the Top Stories endpoint
//  https://api.nytimes.com/svc/topstories/v2/home.json
//

data class Response(
    val copyright: String,
    val num_results: Int,
    val results: List<Article>,
    val status: String
)

data class Article(
    val `abstract`: String?,
    val adx_keywords: String?,
    val asset_id: Long?,
    val byline: String?,
    val column: Any?,
    val des_facet: List<String>?,
    val eta_id: Int?,
    val geo_facet: List<String>?,
    val id: Long?,

    ///...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//
// And this is for the Article Search Endpoint
// https://api.nytimes.com/svc/search/v2/articlesearch.json
//

data class Response(
    val copyright: String,
    val response: ResponseX,
    val status: String
)

data class ResponseX(
    val docs: List<Doc>,
    val meta: Meta
)

data class Doc(
    val _id: String?,
    val `abstract`: String?,
    val byline: Byline?,
    val document_type: String?,
    val headline: Headline?,
    val keywords: List<Keyword>?,
    val lead_paragraph: String?,
    val multimedia: List<Multimedia>?,
    val news_desk: String?,

    //... 

I noticed that I could get a common set of parameters like thumbnail image path, title, section..etc out of all of the endpoints. The goal for me was to architect this in such a way that the rest of the entire app didn’t care about which endpoint the Article came from.

The way I solved this was with an abstract base class as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//
// The abstract base class that ties together the endpoints that
// provide news documents, albeit in subtly different ways.
//

abstract class AbstractNewsDoc {
    abstract fun getThumbnailUrl() : String?
    abstract fun getSectionOrType() : String
    abstract fun getTitle() : String
    abstract fun getUri(): String
    abstract fun getPublishedDate(): String

    override fun equals(other: Any?): Boolean {
        return this.hashCode() == other.hashCode()
    }

    override fun hashCode(): Int {
        return getThumbnailUrl().hashCode() * getSectionOrType().hashCode() * getTitle().hashCode() *
                getUri().hashCode() * getPublishedDate().hashCode()
    }

    fun getHumanizedPublishedDate(): String {
        val dt = DateTime.parse(getPublishedDate())
        val formatterOut = DateTimeFormat.forPattern("M/d/yy")
        return dt.toString(formatterOut)
    }

}

Notice that there is some functionality that the base class implements (hence it being abstract, and not an interface), and some functionality that the subclasses implement. All of the endpoints produced something that would lead to a:

  • thumbnail url
  • section or document type (functions like a subtitle in the app)
  • title
  • uri
  • published date

Here is an example implementation of getThumbnailUrl() in two different base classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// The search endpoint (code omitted for clarity)
data class Doc(): AbstractNewsDoc {
 
 override fun getThumbnailUrl():String? {

        val url = multimedia
            ?.filter { it.format == "Standard Thumbnail" }
            ?.getOrNull(0)
            ?.url

        return url
    
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// and the Top Stories endpoint (code omitted for clarity)
data class Article(): AbstractNewsDoc { 
 
     override fun getThumbnailUrl(): String? {
 
        val path = multimedia
            ?.filter { it.subtype == "thumbnail" }
            ?.getOrNull(0)
            ?.url

        return relStatic(path)  // relstatic prepends a base URL
 
    }

}

The upshot of this work was that throughout the rest of the app, I could pass around Lists of AbstracNewsDocs, and not worry about what endpoint the data came from.

So the LiveData that drives the list of articles (i.e. for the RecyclerView) is:

1
2
3
4
5
6
7
8
9
10
typealias ArticlesOrError = Pair<Throwable?, List<AbstractNewsDoc>?>

class MainViewModel(private val repo: IRepository) : ViewModel() {
    
    private val newsFeed: LiveData<ArticlesOrError>
    
    fun getNewsFeed() : LiveData<ArticlesOrError> { return newsFeed }

    //...
}

So I accomplished what I wanted to accomplish, passing around articles from three separate and subtly different endpoints as conforming to one single base class.