Purpose of the Fetcher

  • Data Retrieval: The Fetcher abstracts the logic of fetching data from remote sources, allowing the Store to request data without needing to know the details of how and where the data is obtained.
  • Error Handling: It provides a consistent way to handle errors that may occur during data retrieval.
  • Multiple Responses: The Fetcher supports both single and multiple responses per request, accommodating various network protocols like HTTP and WebSockets.
  • Fallback Mechanisms: It allows specifying fallback Fetchers to use alternative data sources if the primary fetch fails.

APIs

Fetcher

Fetcher has the following structure:

interface Fetcher<Key : Any, Network : Any> {
    val name: String?
    val fallback: Fetcher<Key, Network>?
    operator fun invoke(key: Key): Flow<FetcherResult<Network>>
}
Key
Any
required

The type representing the key used to identify the data to fetch. For example, if fetching a list of posts, this could be an Int representing the post ID.

Network
Any
required

The type representing the data fetched from the remote source. For example, if fetching a list of posts, this could be List<Post> representing the list of posts.

name
String?

An optional unique name for the Fetcher, useful when differentiating between multiple fetchers, particularly when using fallbacks.

fallback
Fetcher<Key, Network>?

An optional Fetcher to be used if the parent Fetcher fails.

invoke(key: Key)
Flow<FetcherResult<Network>>

A function that takes a key and returns a Flow<FetcherResult<Network>>, representing the asynchronous stream of fetched data or errors.

FetcherResult

When the Fetcher retrieves data, it wraps the result in a FetcherResult, which can represent either a successful data retrieval or an error.

sealed class FetcherResult<out Network : Any>

FetcherResult.Data

Represents a successful fetch.

sealed class FetcherResult<out Network : Any> {
    data class Data<Network : Any>(val value: Network, val origin: String? = null) : FetcherResult<Network>()
}
value
Network
required

The fetched data.

origin
String?

An optional string to identify the source of the data.

FetcherResult.Error

Represents an error that occurred during fetching.

sealed class FetcherResult<out Network : Any> {
    data class Data<Network : Any>(val value: Network, val origin: String? = null) : FetcherResult<Network>()

    sealed class Error : FetcherResult<Nothing>()
}
FetcherResult.Error.Exception

Used to represent an exception that occurred.

sealed class FetcherResult<out Network : Any> {
    data class Data<Network : Any>(val value: Network, val origin: String? = null) : FetcherResult<Network>()

    sealed class Error : FetcherResult<Nothing>() {
        data class Exception(val error: Throwable) : Error()
    }
}
error
Throwable
required

The exception that occurred.

FetcherResult.Error.Message

Used to represent an error that occurred without an exception being thrown.

sealed class FetcherResult<out Network : Any> {
    data class Data<Network : Any>(val value: Network, val origin: String? = null) : FetcherResult<Network>()

    sealed class Error : FetcherResult<Nothing>() {
        data class Exception(val error: Throwable) : Error()

        data class Message(val message: String) : Error()
    }
}
message
String
required

The error message.

FetcherResult.Error.Custom

Used to represent a custom error. This is useful when the network returns an error object that is not an exception. For example, a union type returned from a gRPC call.

sealed class FetcherResult<out Network : Any> {
    data class Data<Network : Any>(val value: Network, val origin: String? = null) : FetcherResult<Network>()

    sealed class Error : FetcherResult<Nothing>() {
        data class Exception(val error: Throwable) : Error()

        data class Message(val message: String) : Error()

        data class Custom<E : Any>(val error: E) : Error()
    }
}
E
Any
required

The type representing the custom error. For example, if fetching a list of posts, this could be PostNetworkError.

error
E
required

The custom error.

Data Flow

1

Data Request

Application code requests data from the Store.

Extension for Making a Cached Data Request:

suspend fun <Key : Any, Output : Any> Store<Key, Output>.get(key: Key) =
    stream(StoreReadRequest.cached(key, refresh = false))
        .filterNot { it is StoreReadResponse.Loading || it is StoreReadResponse.NoNewData }
        .first()
        .requireData()

Extension for Making a Fresh Data Request:

suspend fun <Key : Any, Output : Any> Store<Key, Output>.fresh(key: Key) =
  stream(StoreReadRequest.fresh(key))
      .filterNot { it is StoreReadResponse.Loading || it is StoreReadResponse.NoNewData }
      .first()
      .requireData()
2

Client-Side Checks Conditional On Request Type

Fresh Data Requests Bypass Client-Side Checks:

If the request is for fresh data, the Store bypasses the Memory Cache, Source Of Truth, and Validator and invokes the Fetcher immediately.

A

Cache Check

If the request is for cached data, the Store checks the Memory Cache for the requested data. If the Memory Cache does not contain the data, the Store checks the Source Of Truth, if the Store is configured with a Source Of Truth.

B

Validation

If the data is missing or determined invalid by the Validator, the Store invokes the Fetcher.

3

Data Fetching

The Fetcher retrieves data from the remote source, emitting FetcherResult objects.

4

Error Handling

If an error occurs, the Fetcher emits a FetcherResult.Error, which the Store can handle appropriately.

5

Data Storage

Successful data is written to the Source Of Truth and Memory Cache.

6

Data Delivery

The Store provides the data to the application in the form of a StoreReadResponse.