# [Philipp Lackner - Android Testing](https://www.youtube.com/watch?v=EkfVL5vCDmo&list=PLQkwcJG4YTCSYJ13G4kVIJ10X5zisB2Lq) ```Resource.kt data class Resource<out T>(val status: Status, val data: T?, val message: String?) { companion object { fun <T> success(data: T?): Resource<T> { return Resource(Status.SUCCESS, data, null) } fun <T> error(msg: String, data: T?): Resource<T> { return Resource(Status.ERROR, data, msg) } fun <T> loading(data: T?): Resource<T> { return Resource(Status.LOADING, data, null) } } } enum class Status { SUCCESS, ERROR, LOADING } ``` ## [How to Build an MVI Clean Code Weather App](https://www.youtube.com/watch?v=eAbKK7JNxCE) ```Resource.kt sealed class Resource<T>(val data: T? = null, val message: String? = null) { class Success<T>(data: T?): Resource<T>(data) class Error<T>(message: String, data: T? = null): Resource<T>(data, message) class Loading<T>(data: T? = null): Resource<T>(data) } ``` 其他出處 [MindOrks - Using Retrofit with Kotlin Coroutines in Android]([MindOrks - Using Retrofit with Kotlin Coroutines in Android](https://blog.mindorks.com/using-retrofit-with-kotlin-coroutines-in-android/)/) --- # [Renaro Santos - Android MVVM + Clean Architecture](https://www.youtube.com/playlist?list=PLMVOtgo-eaZ5AoTBQOXr01O_OQdvcuzQt) ```Result.kt sealed class Result<out T> { data class Error(val exception: Exception) : Result<Nothing>() data class Success<T>(val data: T) : Result<T>() } ``` # Kotlin [Result](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/) # [套用 Repository Pattern 完成最後架構](https://ithelp.ithome.com.tw/articles/10195487) Resource ```Resource.java public class Resource<T> { @NonNull public final Status status; @Nullable public final T data; @Nullable public final String message; public Resource(@NonNull Status status, @Nullable T data, @Nullable String message) { this.status = status; this.data = data; this.message = message; } public static <T> Resource<T> success(@Nullable T data) { return new Resource<>(SUCCESS, data, null); } public static <T> Resource<T> error(@Nullable T data, String msg) { return new Resource<>(ERROR, data, msg); } public static <T> Resource<T> loading(@Nullable T data) { return new Resource<>(LOADING, data, null); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Resource<?> resource = (Resource<?>) o; return Objects.equals(status, resource.status) && Objects.equals(message, resource.message) && Objects.equals(data, resource.data); } @Override public int hashCode() { int result = status.hashCode(); result = 31 * result + (message != null ? message.hashCode() : 0); result = 31 * result + (data != null ? data.hashCode() : 0); return result; } @Override public String toString() { return "Resource{" + "status=" + status + ", message='" + message + '\'' + ", data=" + data + '}'; } } ``` Status ```Status.java public enum Status { SUCCESS, ERROR, LOADING } ``` # Android ## Architecture ### nowinandroid ### sunflower ### compose-samples Jetcaster ``` sealed class PodcastRssResponse { data class Error( val throwable: Throwable?, ) : PodcastRssResponse() data class Success( val podcast: Podcast, val episodes: List<Episode>, val categories: Set<Category> ) : PodcastRssResponse() } ``` # ListenableWorker ```ListenableWorker.Result.java public abstract static class Result { /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed successfully. Any work that depends on this can be executed as long as all of * its other dependencies and constraints are met. * * @return An instance of {@link Result} indicating successful execution of work */ @NonNull public static Result success() { return new Success(); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed successfully. Any work that depends on this can be executed as long as all of * its other dependencies and constraints are met. * * @param outputData A {@link Data} object that will be merged into the input Data of any * OneTimeWorkRequest that is dependent on this work * @return An instance of {@link Result} indicating successful execution of work */ @NonNull public static Result success(@NonNull Data outputData) { return new Success(outputData); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * encountered a transient failure and should be retried with backoff specified in * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}. * * @return An instance of {@link Result} indicating that the work needs to be retried */ @NonNull public static Result retry() { return new Retry(); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed with a permanent failure. Any work that depends on this will also be marked as * failed and will not be run. <b>If you need child workers to run, you need to use * {@link #success()} or {@link #success(Data)}</b>; failure indicates a permanent stoppage * of the chain of work. * * @return An instance of {@link Result} indicating failure when executing work */ @NonNull public static Result failure() { return new Failure(); } /** * Returns an instance of {@link Result} that can be used to indicate that the work * completed with a permanent failure. Any work that depends on this will also be marked as * failed and will not be run. <b>If you need child workers to run, you need to use * {@link #success()} or {@link #success(Data)}</b>; failure indicates a permanent stoppage * of the chain of work. * * @param outputData A {@link Data} object that can be used to keep track of why the work * failed * @return An instance of {@link Result} indicating failure when executing work */ @NonNull public static Result failure(@NonNull Data outputData) { return new Failure(outputData); } /** * @return The output {@link Data} which will be merged into the input {@link Data} of * any {@link OneTimeWorkRequest} that is dependent on this work request. */ @NonNull public abstract Data getOutputData(); /** */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) Result() { // Restricting access to the constructor, to give Result a sealed class // like behavior. } /** * Used to indicate that the work completed successfully. Any work that depends on this * can be executed as long as all of its other dependencies and constraints are met. * */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static final class Success extends Result { private final Data mOutputData; public Success() { this(Data.EMPTY); } /** * @param outputData A {@link Data} object that will be merged into the input Data of * any OneTimeWorkRequest that is dependent on this work */ public Success(@NonNull Data outputData) { super(); mOutputData = outputData; } @Override public @NonNull Data getOutputData() { return mOutputData; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Success success = (Success) o; return mOutputData.equals(success.mOutputData); } @Override public int hashCode() { String name = Success.class.getName(); return 31 * name.hashCode() + mOutputData.hashCode(); } @NonNull @Override public String toString() { return "Success {" + "mOutputData=" + mOutputData + '}'; } } /** * Used to indicate that the work completed with a permanent failure. Any work that depends * on this will also be marked as failed and will not be run. <b>If you need child workers * to run, you need to return {@link Result.Success}</b>; failure indicates a permanent * stoppage of the chain of work. * */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static final class Failure extends Result { private final Data mOutputData; public Failure() { this(Data.EMPTY); } /** * @param outputData A {@link Data} object that can be used to keep track of why the * work failed */ public Failure(@NonNull Data outputData) { super(); mOutputData = outputData; } @Override public @NonNull Data getOutputData() { return mOutputData; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Failure failure = (Failure) o; return mOutputData.equals(failure.mOutputData); } @Override public int hashCode() { String name = Failure.class.getName(); return 31 * name.hashCode() + mOutputData.hashCode(); } @NonNull @Override public String toString() { return "Failure {" + "mOutputData=" + mOutputData + '}'; } } /** * Used to indicate that the work encountered a transient failure and should be retried with * backoff specified in * {@link WorkRequest.Builder#setBackoffCriteria(BackoffPolicy, long, TimeUnit)}. * */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static final class Retry extends Result { public Retry() { super(); } @Override public boolean equals(Object o) { if (this == o) return true; // We are treating all instances of Retry as equivalent. return o != null && getClass() == o.getClass(); } @Override public int hashCode() { String name = Retry.class.getName(); return name.hashCode(); } @NonNull @Override public Data getOutputData() { return Data.EMPTY; } @NonNull @Override public String toString() { return "Retry"; } } } ``` # PagingSource ```PagingSource.LoadResult.kt public sealed class LoadResult<Key : Any, Value : Any> { /** * Error result object for [PagingSource.load]. * * This return type indicates an expected, recoverable error (such as a network load * failure). This failure will be forwarded to the UI as a [LoadState.Error], and may be * retried. * * @sample androidx.paging.samples.pageKeyedPagingSourceSample */ public data class Error<Key : Any, Value : Any>( val throwable: Throwable ) : LoadResult<Key, Value>() { override fun toString(): String { return """LoadResult.Error( | throwable: $throwable |) """.trimMargin() } } /** * Invalid result object for [PagingSource.load] * * This return type can be used to terminate future load requests on this [PagingSource] * when the [PagingSource] is not longer valid due to changes in the underlying dataset. * * For example, if the underlying database gets written into but the [PagingSource] does * not invalidate in time, it may return inconsistent results if its implementation depends * on the immutability of the backing dataset it loads from (e.g., LIMIT OFFSET style db * implementations). In this scenario, it is recommended to check for invalidation after * loading and to return LoadResult.Invalid, which causes Paging to discard any * pending or future load requests to this PagingSource and invalidate it. * * Returning [Invalid] will trigger Paging to [invalidate] this [PagingSource] and * terminate any future attempts to [load] from this [PagingSource] */ public class Invalid<Key : Any, Value : Any> : LoadResult<Key, Value>() { override fun toString(): String { return "LoadResult.Invalid" } } /** * Success result object for [PagingSource.load]. * * As a convenience, iterating on this object will iterate through its loaded [data]. * * @sample androidx.paging.samples.pageKeyedPage * @sample androidx.paging.samples.pageIndexedPage */ public data class Page<Key : Any, Value : Any> constructor( /** * Loaded data */ val data: List<Value>, /** * [Key] for previous page if more data can be loaded in that direction, `null` * otherwise. */ val prevKey: Key?, /** * [Key] for next page if more data can be loaded in that direction, `null` otherwise. */ val nextKey: Key?, /** * Count of items before the loaded data. Must be implemented if * [jumping][PagingSource.jumpingSupported] is enabled. Optional otherwise. */ @IntRange(from = COUNT_UNDEFINED.toLong()) val itemsBefore: Int = COUNT_UNDEFINED, /** * Count of items after the loaded data. Must be implemented if * [jumping][PagingSource.jumpingSupported] is enabled. Optional otherwise. */ @IntRange(from = COUNT_UNDEFINED.toLong()) val itemsAfter: Int = COUNT_UNDEFINED ) : LoadResult<Key, Value>(), Iterable<Value> { /** * Success result object for [PagingSource.load]. * * @param data Loaded data * @param prevKey [Key] for previous page if more data can be loaded in that direction, * `null` otherwise. * @param nextKey [Key] for next page if more data can be loaded in that direction, * `null` otherwise. */ public constructor( data: List<Value>, prevKey: Key?, nextKey: Key? ) : this(data, prevKey, nextKey, COUNT_UNDEFINED, COUNT_UNDEFINED) init { require(itemsBefore == COUNT_UNDEFINED || itemsBefore >= 0) { "itemsBefore cannot be negative" } require(itemsAfter == COUNT_UNDEFINED || itemsAfter >= 0) { "itemsAfter cannot be negative" } } override fun iterator(): Iterator<Value> { return data.listIterator() } override fun toString(): String { return """LoadResult.Page( | data size: ${data.size} | first Item: ${data.firstOrNull()} | last Item: ${data.lastOrNull()} | nextKey: $nextKey | prevKey: $prevKey | itemsBefore: $itemsBefore | itemsAfter: $itemsAfter |) """.trimMargin() } public companion object { public const val COUNT_UNDEFINED: Int = Int.MIN_VALUE @Suppress("MemberVisibilityCanBePrivate") // Prevent synthetic accessor generation. internal val EMPTY = Page(emptyList(), null, null, 0, 0) @Suppress("UNCHECKED_CAST") // Can safely ignore, since the list is empty. internal fun <Key : Any, Value : Any> empty() = EMPTY as Page<Key, Value> } } } ``` # [Kotlin Coroutines](https://developer.android.com/kotlin/coroutines?hl=zh-tw) ```Result.kt sealed class Result<out R> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>() } ``` # Using Retrofit with Kotlin Coroutines ## [MindOrks - Using Retrofit with Kotlin Coroutines in Android](https://blog.mindorks.com/using-retrofit-with-kotlin-coroutines-in-android/) # Shopify - GraphCallResult ``` /** * Represents `GraphQL` operation execution result. */ sealed class GraphCallResult<out T : AbstractResponse<out T>> { /** * `GraphQL` operation result returned for success execution, with the typed operation response that will contain fields that match * exactly the fields requested. */ class Success<T : AbstractResponse<T>>(val response: GraphResponse<T>) : GraphCallResult<T>() /** * `GraphQL` operation result returned for failed execution, with the error caused this call to fail. * * @see GraphError */ class Failure(val error: GraphError) : GraphCallResult<Nothing>() } ```