├── .editorconfig
├── .github
├── renovate.json5
└── workflows
│ ├── check.yml
│ └── deploy.yml
├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
└── inspectionProfiles
│ └── Project_Default.xml
├── LICENSE
├── README-ja.md
├── README.md
├── build.gradle.kts
├── composeApp
├── .gitignore
├── build.gradle.kts
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ ├── kotlin
│ │ └── com
│ │ │ └── kazakago
│ │ │ └── swr
│ │ │ └── compose
│ │ │ └── example
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ └── strings.xml
│ ├── commonMain
│ └── kotlin
│ │ ├── App.kt
│ │ ├── MainScreen.kt
│ │ ├── basic
│ │ ├── ArgumentsScreen.kt
│ │ ├── AutoRevalidationScreen.kt
│ │ ├── ConditionalFetchingScreen.kt
│ │ ├── DataFetchingScreen.kt
│ │ ├── ErrorHandlingScreen.kt
│ │ ├── GlobalConfigurationScreen.kt
│ │ ├── InfinitePaginationScreen.kt
│ │ ├── MutationScreen.kt
│ │ ├── PaginationScreen.kt
│ │ └── PrefetchingScreen.kt
│ │ ├── todolist
│ │ ├── ToDoCreationDialog.kt
│ │ ├── ToDoEditingDialog.kt
│ │ ├── ToDoListScreen.kt
│ │ └── server
│ │ │ ├── MockServer.kt
│ │ │ ├── MockServerAllFailed.kt
│ │ │ ├── MockServerLoadingSlow.kt
│ │ │ ├── MockServerMutationFailed.kt
│ │ │ ├── MockServerRandomFailed.kt
│ │ │ └── MockServerSucceed.kt
│ │ └── ui
│ │ ├── ErrorContent.kt
│ │ ├── LoadingContent.kt
│ │ └── theme
│ │ ├── Color.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ ├── desktopMain
│ └── kotlin
│ │ └── main.kt
│ └── iosMain
│ └── kotlin
│ └── MainViewController.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── iosApp
├── .gitignore
├── Configuration
│ └── Config.xcconfig
├── iosApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── iosApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ └── app-icon-1024.png
│ └── Contents.json
│ ├── ContentView.swift
│ ├── Info.plist
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── iOSApp.swift
├── settings.gradle.kts
└── swr
├── .gitignore
├── api
├── android
│ └── swr.api
├── jvm
│ └── swr.api
└── swr.klib.api
├── build.gradle.kts
└── src
├── androidUnitTest
└── kotlin
│ └── com
│ └── kazakago
│ └── swr
│ └── compose
│ ├── DummyException.kt
│ ├── NetworkRule.kt
│ ├── config
│ ├── GlobalConfigutationsTest.kt
│ └── UseSWRConfigTest.kt
│ ├── immutable
│ └── UseSWRImmutableTest.kt
│ ├── infinite
│ ├── InitialSizeOptionTest.kt
│ ├── PersistSizeOptionTest.kt
│ ├── RevalidateAllOptionTest.kt
│ ├── RevalidateFirstPageOptionTest.kt
│ └── UseSWRInfiniteTest.kt
│ ├── mutation
│ ├── OptimisticOptionTest.kt
│ ├── PopulateCacheOptionTest.kt
│ ├── RevalidateOptionTest.kt
│ ├── RollbackOnErrorOptionTest.kt
│ ├── SWRMutateTest.kt
│ └── ThrowOnErrorOptionTest.kt
│ ├── prefetch
│ └── UsePreloadTest.kt
│ ├── trigger
│ ├── OptimisticOptionTest.kt
│ ├── PopulateCacheOptionTest.kt
│ ├── RevalidateOptionTest.kt
│ ├── RollbackOnErrorOptionTest.kt
│ ├── ThrowOnErrorOptionTest.kt
│ └── UseSWRMutationTest.kt
│ └── validation
│ ├── DedupingIntervalOptionTest.kt
│ ├── ErrorRetryCountOptionTest.kt
│ ├── ErrorRetryIntervalOptionTest.kt
│ ├── FallbackDataOptionTest.kt
│ ├── FallbackOptionTest.kt
│ ├── FetcherOptionTest.kt
│ ├── FocusThrottleIntervalOptionTest.kt
│ ├── IsPausedOptionTest.kt
│ ├── KeepPreviousDataOptionTest.kt
│ ├── LoadingTimeoutOptionTest.kt
│ ├── OnErrorOptionTest.kt
│ ├── OnErrorRetryOptionTest.kt
│ ├── OnLoadingSlowOptionTest.kt
│ ├── OnSuccessOptionTest.kt
│ ├── RefreshIntervalOptionTest.kt
│ ├── RefreshWhenHiddenOptionTest.kt
│ ├── RefreshWhenOfflineOptionTest.kt
│ ├── RevalidateIfStaleOptionTest.kt
│ ├── RevalidateOnFocusOptionTest.kt
│ ├── RevalidateOnMountOptionTest.kt
│ ├── RevalidateOnReconnectOptionTest.kt
│ ├── ShouldRetryOnErrorOptionTest.kt
│ └── UseSWRTest.kt
└── commonMain
└── kotlin
└── com
└── kazakago
└── swr
└── compose
├── UseSWR.kt
├── UseSWRConfig.kt
├── UseSWRImmutable.kt
├── UseSWRInfinite.kt
├── UseSWRMutation.kt
├── UseSWRPreload.kt
├── cache
├── SWRCache.kt
└── SWRSystemCache.kt
├── config
├── SWRConfig.kt
├── SWRInfiniteConfig.kt
├── SWRMutateConfig.kt
└── SWRTriggerConfig.kt
├── internal
├── DataHolder.kt
├── GlobalKonnection.kt
├── SWRGlobalScope.kt
├── UseSWRInternal.kt
└── UseSWROptions.kt
├── mutate
└── SWRMutate.kt
├── preload
└── SWRPreload.kt
├── retry
└── SWRRetryDefault.kt
├── state
├── SWRConfigState.kt
├── SWRInfiniteState.kt
├── SWRMutationState.kt
└── SWRState.kt
├── trigger
├── SWRReset.kt
└── SWRTrigger.kt
└── validate
├── SWRValidate.kt
└── SWRValidateOptions.kt
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | insert_final_newline = true
3 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | }
4 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: Check
2 |
3 | on:
4 | push:
5 | branches: [ main, production ]
6 | pull_request:
7 | workflow_dispatch:
8 |
9 | jobs:
10 | check:
11 | runs-on: macos-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - run: ./gradlew check
17 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | tags: [ "*.*.*" ]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | deploy:
10 | runs-on: macos-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
16 | env:
17 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
18 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
19 | SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
20 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
21 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
22 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea/*
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | !.idea/codeStyles/
12 | !.idea/inspectionProfiles/
13 | !.idea/runConfigurations/
14 | .kotlin/
15 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlin.multiplatform) apply false
3 | alias(libs.plugins.kotlin.android) apply false
4 | alias(libs.plugins.compose) apply false
5 | alias(libs.plugins.compose.compiler) apply false
6 | alias(libs.plugins.dokka) apply false
7 | alias(libs.plugins.android.application) apply false
8 | alias(libs.plugins.android.library) apply false
9 | alias(libs.plugins.binary.compatibility.validator) apply false
10 | alias(libs.plugins.nexus.publish)
11 | }
12 |
13 | nexusPublishing {
14 | repositories {
15 | sonatype {
16 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
17 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
18 | username.set(System.getenv("SONATYPE_USERNAME") ?: findProperty("sonatype.username").toString())
19 | password.set(System.getenv("SONATYPE_PASSWORD") ?: findProperty("sonatype.password").toString())
20 | stagingProfileId.set(System.getenv("SONATYPE_STAGING_PROFILE_ID") ?: findProperty("sonatype.stagingProfileId").toString())
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/composeApp/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/composeApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
3 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
4 |
5 | plugins {
6 | alias(libs.plugins.kotlin.multiplatform)
7 | alias(libs.plugins.android.application)
8 | alias(libs.plugins.compose)
9 | alias(libs.plugins.compose.compiler)
10 | }
11 |
12 | kotlin {
13 | androidTarget {
14 | @OptIn(ExperimentalKotlinGradlePluginApi::class)
15 | compilerOptions {
16 | jvmTarget.set(JvmTarget.JVM_11)
17 | }
18 | }
19 |
20 | jvm("desktop")
21 |
22 | listOf(
23 | iosX64(),
24 | iosArm64(),
25 | iosSimulatorArm64()
26 | ).forEach { iosTarget ->
27 | iosTarget.binaries.framework {
28 | baseName = "ComposeApp"
29 | isStatic = true
30 | }
31 | }
32 |
33 | sourceSets {
34 | commonMain.dependencies {
35 | implementation(compose.material3)
36 | implementation(compose.components.uiToolingPreview)
37 | implementation(libs.kotlinx.datetime)
38 | implementation(libs.androidx.navigation.compose)
39 | implementation(projects.swr)
40 | }
41 | androidMain.dependencies {
42 | implementation(compose.preview)
43 | implementation(libs.androidx.activity.compose)
44 | }
45 | val desktopMain by getting
46 | desktopMain.dependencies {
47 | implementation(compose.desktop.currentOs)
48 | }
49 | }
50 | }
51 |
52 | android {
53 | namespace = "com.kazakago.swr.compose.example"
54 | compileSdk = libs.versions.compileSdk.get().toInt()
55 |
56 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
57 | sourceSets["main"].res.srcDirs("src/androidMain/res")
58 | sourceSets["main"].resources.srcDirs("src/commonMain/resources")
59 |
60 | defaultConfig {
61 | applicationId = "com.kazakago.swr.compose"
62 | minSdk = libs.versions.minSdk.get().toInt()
63 | targetSdk = libs.versions.targetSdk.get().toInt()
64 | versionCode = 1
65 | versionName = "1.0"
66 | }
67 | packaging {
68 | resources {
69 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
70 | }
71 | }
72 | buildTypes {
73 | getByName("release") {
74 | isMinifyEnabled = false
75 | }
76 | }
77 | compileOptions {
78 | sourceCompatibility = JavaVersion.VERSION_11
79 | targetCompatibility = JavaVersion.VERSION_11
80 | }
81 | buildFeatures {
82 | compose = true
83 | }
84 | dependencies {
85 | debugImplementation(compose.uiTooling)
86 | }
87 | }
88 |
89 | compose.desktop {
90 | application {
91 | mainClass = "MainKt"
92 |
93 | nativeDistributions {
94 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
95 | packageName = "com.kazakago.swr.compose.example"
96 | packageVersion = "1.0.0"
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/kazakago/swr/compose/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.example
2 |
3 | import App
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.tooling.preview.Preview
9 |
10 | class MainActivity : ComponentActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContent {
14 | App()
15 | }
16 | }
17 | }
18 |
19 | @Preview
20 | @Composable
21 | fun AppAndroidPreview() {
22 | App()
23 | }
24 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SWR Compose
3 |
4 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/App.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.Composable
2 | import androidx.compose.runtime.CompositionLocalProvider
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.compose.runtime.remember
5 | import androidx.compose.runtime.rememberCoroutineScope
6 | import androidx.navigation.compose.NavHost
7 | import androidx.navigation.compose.composable
8 | import androidx.navigation.compose.rememberNavController
9 | import basic.ArgumentsScreen
10 | import basic.AutoRevalidationScreen
11 | import basic.ConditionalFetchingScreen
12 | import basic.DataFetchingScreen
13 | import basic.ErrorHandlingScreen
14 | import basic.GlobalConfigurationScreen
15 | import basic.InfinitePaginationScreen
16 | import basic.MutationScreen
17 | import basic.PaginationScreen
18 | import basic.PrefetchingNextScreen
19 | import basic.PrefetchingScreen
20 | import org.jetbrains.compose.ui.tooling.preview.Preview
21 | import todolist.ToDoListScreen
22 | import todolist.server.LocalMockServer
23 | import todolist.server.MockServer
24 | import todolist.server.MockServerSucceed
25 | import ui.theme.AppTheme
26 |
27 | @Composable
28 | @Preview
29 | fun App() {
30 | AppTheme {
31 | val navController = rememberNavController()
32 | val scope = rememberCoroutineScope()
33 | val mockServer = remember { mutableStateOf(MockServerSucceed) }
34 | val isClearCache = remember { mutableStateOf(true) }
35 | CompositionLocalProvider(LocalMockServer provides mockServer.value) {
36 | NavHost(navController = navController, startDestination = "main") {
37 | composable("main") { MainScreen(navController, mockServer, isClearCache) }
38 | composable("data_fetching") { DataFetchingScreen(navController) }
39 | composable("global_configuration") { GlobalConfigurationScreen(navController) }
40 | composable("error_handling") { ErrorHandlingScreen(navController) }
41 | composable("auto_revalidation") { AutoRevalidationScreen(navController) }
42 | composable("conditional_fetching") { ConditionalFetchingScreen(navController) }
43 | composable("arguments") { ArgumentsScreen(navController) }
44 | composable("mutation") { MutationScreen(navController) }
45 | composable("todolist") { ToDoListScreen(navController) }
46 | composable("pagination") { PaginationScreen(navController) }
47 | composable("infinite_pagination") { InfinitePaginationScreen(navController) }
48 | composable("prefetching") { PrefetchingScreen(navController, scope) }
49 | composable("prefetching_next") { PrefetchingNextScreen(navController, scope) }
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/ArgumentsScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
10 | import androidx.compose.material3.ExperimentalMaterial3Api
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.IconButton
13 | import androidx.compose.material3.Scaffold
14 | import androidx.compose.material3.Text
15 | import androidx.compose.material3.TopAppBar
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.navigation.NavController
20 | import ui.LoadingContent
21 | import com.kazakago.swr.compose.useSWR
22 | import kotlinx.coroutines.delay
23 |
24 | private val fetcher: suspend (key: String) -> String = { key ->
25 | delay(1000)
26 | "Argument is '$key'"
27 | }
28 |
29 | @Composable
30 | @OptIn(ExperimentalMaterial3Api::class)
31 | fun ArgumentsScreen(navController: NavController) {
32 | val (data1) = useSWR(key = "/arguments/google", fetcher = fetcher)
33 | val (data2) = useSWR(key = "/arguments/apple", fetcher = fetcher)
34 | Scaffold(
35 | topBar = {
36 | TopAppBar(
37 | title = { Text("Arguments") },
38 | navigationIcon = {
39 | IconButton(onClick = navController::popBackStack) {
40 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
41 | }
42 | },
43 | )
44 | },
45 | ) {
46 | Box(
47 | modifier = Modifier
48 | .fillMaxSize()
49 | .padding(it),
50 | contentAlignment = Alignment.Center,
51 | ) {
52 | if (data1 == null || data2 == null) {
53 | LoadingContent()
54 | } else {
55 | Column(
56 | modifier = Modifier.fillMaxSize(),
57 | verticalArrangement = Arrangement.Center,
58 | horizontalAlignment = Alignment.CenterHorizontally,
59 | ) {
60 | Text(data1)
61 | Text(data2)
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/AutoRevalidationScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.IconButton
12 | import androidx.compose.material3.LinearProgressIndicator
13 | import androidx.compose.material3.Scaffold
14 | import androidx.compose.material3.Text
15 | import androidx.compose.material3.TopAppBar
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.navigation.NavController
20 | import com.kazakago.swr.compose.useSWR
21 | import kotlinx.coroutines.delay
22 | import kotlinx.datetime.Clock
23 | import kotlinx.datetime.format
24 | import kotlinx.datetime.format.DateTimeComponents
25 | import ui.LoadingContent
26 | import kotlin.time.Duration.Companion.seconds
27 |
28 | private val fetcher: suspend (key: String) -> String = {
29 | delay(1000)
30 | Clock.System.now().format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET)
31 | }
32 |
33 | @Composable
34 | @OptIn(ExperimentalMaterial3Api::class)
35 | fun AutoRevalidationScreen(navController: NavController) {
36 | val (data, _, _, isValidating) = useSWR(key = "/auto_revalidation", fetcher = fetcher) {
37 | revalidateOnMount = true // default is null
38 | revalidateIfStale = true // default is true
39 | revalidateOnFocus = true // default is true
40 | revalidateOnReconnect = true // default is true
41 | refreshInterval = 10.seconds // default is 0.seconds (=disable)
42 | refreshWhenHidden = false // default is false
43 | refreshWhenOffline = false // default is false
44 | }
45 | Scaffold(
46 | topBar = {
47 | TopAppBar(
48 | title = { Text("Auto Revalidation") },
49 | navigationIcon = {
50 | IconButton(onClick = navController::popBackStack) {
51 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
52 | }
53 | },
54 | )
55 | },
56 | ) {
57 | Box(
58 | modifier = Modifier
59 | .fillMaxSize()
60 | .padding(it),
61 | contentAlignment = Alignment.TopCenter,
62 | ) {
63 | if (data == null) {
64 | LoadingContent()
65 | } else {
66 | if (isValidating) {
67 | LinearProgressIndicator(Modifier.fillMaxWidth())
68 | }
69 | Box(
70 | modifier = Modifier.fillMaxSize(),
71 | contentAlignment = Alignment.Center
72 | ) {
73 | Text(data)
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/ConditionalFetchingScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.icons.Icons
9 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
10 | import androidx.compose.material3.CircularProgressIndicator
11 | import androidx.compose.material3.ExperimentalMaterial3Api
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.IconButton
14 | import androidx.compose.material3.Scaffold
15 | import androidx.compose.material3.Text
16 | import androidx.compose.material3.TopAppBar
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.navigation.NavController
21 | import ui.LoadingContent
22 | import com.kazakago.swr.compose.useSWR
23 | import kotlinx.coroutines.delay
24 |
25 | private val userFetcher: suspend (key: String) -> String = {
26 | delay(1000)
27 | "User1"
28 | }
29 |
30 | private val projectsFetcher: suspend (key: String) -> List = {
31 | delay(1000)
32 | listOf("Project1", "Project2", "Project3")
33 | }
34 |
35 | @Composable
36 | @OptIn(ExperimentalMaterial3Api::class)
37 | fun ConditionalFetchingScreen(navController: NavController) {
38 | val (user, _, _) = useSWR(key = "/confidential_fetching/user", fetcher = userFetcher)
39 | val (projects, _, _) = useSWR(key = if (user != null) "/confidential_fetching/$user/projects" else null, fetcher = projectsFetcher)
40 | Scaffold(
41 | topBar = {
42 | TopAppBar(
43 | title = { Text("Conditional Fetching") },
44 | navigationIcon = {
45 | IconButton(onClick = navController::popBackStack) {
46 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
47 | }
48 | },
49 | )
50 | },
51 | ) {
52 | Box(
53 | modifier = Modifier
54 | .fillMaxSize()
55 | .padding(it),
56 | contentAlignment = Alignment.Center,
57 | ) {
58 | if (user == null) {
59 | LoadingContent()
60 | } else {
61 | Column(
62 | modifier = Modifier.fillMaxSize(),
63 | verticalArrangement = Arrangement.Center,
64 | horizontalAlignment = Alignment.CenterHorizontally,
65 | ) {
66 | Text(user)
67 | if (projects == null) {
68 | CircularProgressIndicator()
69 | } else {
70 | Text(projects.joinToString(", "))
71 | }
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/DataFetchingScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
8 | import androidx.compose.material3.ExperimentalMaterial3Api
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.IconButton
11 | import androidx.compose.material3.Scaffold
12 | import androidx.compose.material3.Text
13 | import androidx.compose.material3.TopAppBar
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.navigation.NavController
18 | import ui.ErrorContent
19 | import ui.LoadingContent
20 | import com.kazakago.swr.compose.useSWR
21 | import kotlinx.coroutines.delay
22 |
23 | private val fetcher: suspend (key: String) -> String = {
24 | delay(1000)
25 | "Hello world!"
26 | }
27 |
28 | @Composable
29 | @OptIn(ExperimentalMaterial3Api::class)
30 | fun DataFetchingScreen(navController: NavController) {
31 | val (data, error) = useSWR(key = "/data_fetching", fetcher = fetcher)
32 | Scaffold(
33 | topBar = {
34 | TopAppBar(
35 | title = { Text("Data Fetching") },
36 | navigationIcon = {
37 | IconButton(onClick = navController::popBackStack) {
38 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
39 | }
40 | },
41 | )
42 | },
43 | ) {
44 | Box(
45 | modifier = Modifier
46 | .fillMaxSize()
47 | .padding(it),
48 | contentAlignment = Alignment.Center,
49 | ) {
50 | if (error != null) {
51 | ErrorContent()
52 | } else if (data == null) {
53 | LoadingContent()
54 | } else {
55 | Text(data)
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/ErrorHandlingScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
8 | import androidx.compose.material3.ExperimentalMaterial3Api
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.IconButton
11 | import androidx.compose.material3.Scaffold
12 | import androidx.compose.material3.SnackbarHost
13 | import androidx.compose.material3.SnackbarHostState
14 | import androidx.compose.material3.Text
15 | import androidx.compose.material3.TopAppBar
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.runtime.rememberCoroutineScope
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.navigation.NavController
22 | import com.kazakago.swr.compose.useSWR
23 | import kotlinx.coroutines.delay
24 | import kotlinx.coroutines.launch
25 | import ui.ErrorContent
26 | import ui.LoadingContent
27 | import kotlin.time.Duration.Companion.seconds
28 |
29 | private val fetcher: suspend (key: String) -> String = {
30 | delay(1000)
31 | throw NullPointerException()
32 | }
33 |
34 | @Composable
35 | @OptIn(ExperimentalMaterial3Api::class)
36 | fun ErrorHandlingScreen(navController: NavController) {
37 | val scope = rememberCoroutineScope()
38 | val snackbarHostState = remember { SnackbarHostState() }
39 | val (data, error, _, _, mutate) = useSWR(key = "/error_handling", fetcher = fetcher) {
40 | shouldRetryOnError = true // default is true
41 | errorRetryCount = 3 // default is null
42 | errorRetryInterval = 5.seconds // default is 5.seconds
43 | onError = { error, key, config -> // default is null
44 | scope.launch {
45 | snackbarHostState.showSnackbar(error.toString())
46 | }
47 | }
48 | // onErrorRetry = { _, _, _, _, _ -> // default is `com.kazakago.compose.swr.retry.SWRRetryDefault.kt`
49 | // }
50 | }
51 | Scaffold(
52 | topBar = {
53 | TopAppBar(
54 | title = { Text("Error Handling") },
55 | navigationIcon = {
56 | IconButton(onClick = navController::popBackStack) {
57 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
58 | }
59 | },
60 | )
61 | },
62 | snackbarHost = {
63 | SnackbarHost(snackbarHostState)
64 | },
65 | ) {
66 | Box(
67 | modifier = Modifier
68 | .fillMaxSize()
69 | .padding(it),
70 | contentAlignment = Alignment.Center,
71 | ) {
72 | if (error != null) {
73 | ErrorContent { scope.launch { mutate() } }
74 | } else if (data == null) {
75 | LoadingContent()
76 | } else {
77 | Text(data)
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/GlobalConfigurationScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
9 | import androidx.compose.material3.ExperimentalMaterial3Api
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.IconButton
12 | import androidx.compose.material3.Scaffold
13 | import androidx.compose.material3.SnackbarHost
14 | import androidx.compose.material3.SnackbarHostState
15 | import androidx.compose.material3.Text
16 | import androidx.compose.material3.TopAppBar
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.remember
19 | import androidx.compose.runtime.rememberCoroutineScope
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.navigation.NavController
23 | import com.kazakago.swr.compose.config.SWRConfig
24 | import com.kazakago.swr.compose.useSWR
25 | import kotlinx.coroutines.delay
26 | import kotlinx.coroutines.launch
27 | import ui.LoadingContent
28 | import kotlin.time.Duration.Companion.seconds
29 |
30 | @Composable
31 | @OptIn(ExperimentalMaterial3Api::class)
32 | fun GlobalConfigurationScreen(navController: NavController) {
33 | val snackbarHostState = remember { SnackbarHostState() }
34 | val scope = rememberCoroutineScope()
35 | SWRConfig(options = {
36 | refreshInterval = 3.seconds
37 | fetcher = { key ->
38 | delay(1000)
39 | "Response of $key"
40 | }
41 | onError = { error, _, _ ->
42 | scope.launch {
43 | snackbarHostState.showSnackbar(error.toString())
44 | }
45 | }
46 | }) {
47 | val (events) = useSWR(key = "/global_configuration/events")
48 | val (projects) = useSWR(key = "/global_configuration/projects")
49 | Scaffold(
50 | topBar = {
51 | TopAppBar(
52 | title = { Text("Global Configuration") },
53 | navigationIcon = {
54 | IconButton(onClick = navController::popBackStack) {
55 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
56 | }
57 | },
58 | )
59 | },
60 | snackbarHost = {
61 | SnackbarHost(snackbarHostState)
62 | },
63 | ) {
64 | Box(
65 | modifier = Modifier
66 | .fillMaxSize()
67 | .padding(it),
68 | contentAlignment = Alignment.Center,
69 | ) {
70 | if (events == null || projects == null) {
71 | LoadingContent()
72 | } else {
73 | Column {
74 | Text(events)
75 | Text(projects)
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/MutationScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
11 | import androidx.compose.material3.Button
12 | import androidx.compose.material3.ExperimentalMaterial3Api
13 | import androidx.compose.material3.Icon
14 | import androidx.compose.material3.IconButton
15 | import androidx.compose.material3.Scaffold
16 | import androidx.compose.material3.Text
17 | import androidx.compose.material3.TopAppBar
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.rememberCoroutineScope
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.navigation.NavController
23 | import ui.LoadingContent
24 | import com.kazakago.swr.compose.useSWR
25 | import kotlinx.coroutines.delay
26 | import kotlinx.coroutines.launch
27 |
28 | private val fetcher: suspend (key: String) -> String = {
29 | delay(1000)
30 | "Fetched Data"
31 | }
32 |
33 | private val mutator: suspend () -> String = {
34 | delay(1000)
35 | "Mutated Data"
36 | }
37 |
38 | @Composable
39 | @OptIn(ExperimentalMaterial3Api::class)
40 | fun MutationScreen(navController: NavController) {
41 | val scope = rememberCoroutineScope()
42 | val (data, _, _, _, mutate) = useSWR(key = "/mutation", fetcher = fetcher)
43 | Scaffold(
44 | topBar = {
45 | TopAppBar(
46 | title = { Text("Mutation") },
47 | navigationIcon = {
48 | IconButton(onClick = navController::popBackStack) {
49 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
50 | }
51 | },
52 | )
53 | },
54 | ) {
55 | Box(
56 | modifier = Modifier
57 | .fillMaxSize()
58 | .padding(it),
59 | contentAlignment = Alignment.Center,
60 | ) {
61 | if (data == null) {
62 | LoadingContent()
63 | } else {
64 | Column(
65 | modifier = Modifier.fillMaxSize(),
66 | verticalArrangement = Arrangement.Center,
67 | horizontalAlignment = Alignment.CenterHorizontally,
68 | ) {
69 | Text(data)
70 | Row {
71 | Button(onClick = {
72 | scope.launch {
73 | mutate(data = mutator) {
74 | optimisticData = "Optimistic Data" // default is null
75 | revalidate = false // default is true
76 | populateCache = true // default is true
77 | rollbackOnError = true // default is true
78 | }
79 | }
80 | }) {
81 | Text("mutate")
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/PaginationScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
11 | import androidx.compose.material3.CircularProgressIndicator
12 | import androidx.compose.material3.ExperimentalMaterial3Api
13 | import androidx.compose.material3.Icon
14 | import androidx.compose.material3.IconButton
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.OutlinedButton
17 | import androidx.compose.material3.Scaffold
18 | import androidx.compose.material3.Text
19 | import androidx.compose.material3.TopAppBar
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.mutableIntStateOf
23 | import androidx.compose.runtime.saveable.rememberSaveable
24 | import androidx.compose.runtime.setValue
25 | import androidx.compose.ui.Alignment
26 | import androidx.compose.ui.Modifier
27 | import androidx.navigation.NavController
28 | import com.kazakago.swr.compose.useSWR
29 | import kotlinx.coroutines.delay
30 |
31 | private val fetcher: suspend (key: String) -> List = { key ->
32 | delay(1000)
33 | List(10) { "$key - $it" }
34 | }
35 |
36 | @Composable
37 | @OptIn(ExperimentalMaterial3Api::class)
38 | fun PaginationScreen(navController: NavController) {
39 | var pageCount by rememberSaveable { mutableIntStateOf(1) }
40 | Scaffold(
41 | topBar = {
42 | TopAppBar(
43 | title = { Text("Pagination") },
44 | navigationIcon = {
45 | IconButton(onClick = navController::popBackStack) {
46 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
47 | }
48 | },
49 | )
50 | },
51 | ) {
52 | Box(
53 | modifier = Modifier
54 | .fillMaxSize()
55 | .padding(it),
56 | contentAlignment = Alignment.Center,
57 | ) {
58 | LazyColumn(Modifier.fillMaxSize()) {
59 | items(pageCount) { page ->
60 | PaginationRow(page = page)
61 | }
62 | item {
63 | Box(
64 | modifier = Modifier.fillMaxWidth(),
65 | contentAlignment = Alignment.Center,
66 | ) {
67 | OutlinedButton(onClick = { pageCount += 1 }) {
68 | Text("Load More")
69 | }
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 | @Composable
78 | private fun PaginationRow(page: Int) {
79 | val (list) = useSWR(key = "/pagination/$page", fetcher = fetcher)
80 | Column(
81 | modifier = Modifier.fillMaxWidth(),
82 | horizontalAlignment = Alignment.CenterHorizontally,
83 | ) {
84 | if (list == null) {
85 | CircularProgressIndicator()
86 | } else {
87 | list.forEach { content ->
88 | Text(content, style = MaterialTheme.typography.titleLarge)
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/basic/PrefetchingScreen.kt:
--------------------------------------------------------------------------------
1 | package basic
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
12 | import androidx.compose.material3.Button
13 | import androidx.compose.material3.ExperimentalMaterial3Api
14 | import androidx.compose.material3.Icon
15 | import androidx.compose.material3.IconButton
16 | import androidx.compose.material3.Scaffold
17 | import androidx.compose.material3.Text
18 | import androidx.compose.material3.TopAppBar
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.unit.dp
23 | import androidx.navigation.NavController
24 | import ui.LoadingContent
25 | import com.kazakago.swr.compose.useSWR
26 | import com.kazakago.swr.compose.useSWRPreload
27 | import kotlinx.coroutines.CoroutineScope
28 | import kotlinx.coroutines.delay
29 | import kotlinx.coroutines.launch
30 |
31 | private val fetcher: suspend (key: String) -> String = {
32 | delay(3000)
33 | "Hello world!"
34 | }
35 |
36 | @Composable
37 | @OptIn(ExperimentalMaterial3Api::class)
38 | fun PrefetchingScreen(navController: NavController, scope: CoroutineScope) {
39 | val preload = useSWRPreload(key = "/prefetching", fetcher = fetcher)
40 | Scaffold(
41 | topBar = {
42 | TopAppBar(
43 | title = { Text("Prefetching") },
44 | navigationIcon = {
45 | IconButton(onClick = navController::popBackStack) {
46 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
47 | }
48 | },
49 | )
50 | },
51 | ) {
52 | Column(
53 | modifier = Modifier
54 | .fillMaxSize()
55 | .padding(it),
56 | verticalArrangement = Arrangement.Center,
57 | horizontalAlignment = Alignment.CenterHorizontally,
58 | ) {
59 | Button(onClick = { scope.launch { preload() } }) {
60 | Text("Start prefetch (3s)")
61 | }
62 | Spacer(Modifier.size(16.dp))
63 | Button(onClick = { navController.navigate("prefetching_next") }) {
64 | Text("Move to next Screen")
65 | }
66 | }
67 | }
68 | }
69 |
70 | @Composable
71 | @OptIn(ExperimentalMaterial3Api::class)
72 | fun PrefetchingNextScreen(navController: NavController, scope: CoroutineScope) {
73 | val (data) = useSWR(key = "/prefetching", fetcher = fetcher, scope = scope)
74 | Scaffold(
75 | topBar = {
76 | TopAppBar(
77 | title = { Text("Prefetching Next") },
78 | navigationIcon = {
79 | IconButton(onClick = navController::popBackStack) {
80 | Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
81 | }
82 | },
83 | )
84 | },
85 | ) {
86 | Box(
87 | modifier = Modifier
88 | .fillMaxSize()
89 | .padding(it),
90 | contentAlignment = Alignment.Center,
91 | ) {
92 | if (data == null) {
93 | LoadingContent()
94 | } else {
95 | Text(data)
96 | }
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/ToDoCreationDialog.kt:
--------------------------------------------------------------------------------
1 | package todolist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material3.Button
9 | import androidx.compose.material3.Card
10 | import androidx.compose.material3.OutlinedButton
11 | import androidx.compose.material3.OutlinedTextField
12 | import androidx.compose.material3.Surface
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.mutableStateOf
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.runtime.setValue
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.window.Dialog
22 | import org.jetbrains.compose.ui.tooling.preview.Preview
23 | import ui.theme.AppTheme
24 |
25 | @Composable
26 | fun ToDoCreationDialog(
27 | onSubmit: (text: String) -> Unit,
28 | onCancel: () -> Unit,
29 | ) {
30 | var text by remember { mutableStateOf("") }
31 | Dialog(onDismissRequest = {}) {
32 | Card {
33 | Column(modifier = Modifier.padding(16.dp)) {
34 | OutlinedTextField(
35 | modifier = Modifier.fillMaxWidth(),
36 | value = text,
37 | singleLine = true,
38 | onValueChange = { text = it },
39 | )
40 | Spacer(modifier = Modifier.size(16.dp))
41 | Button(
42 | modifier = Modifier.fillMaxWidth(),
43 | onClick = { onSubmit(text) },
44 | ) {
45 | Text(text = "OK")
46 | }
47 | OutlinedButton(
48 | modifier = Modifier.fillMaxWidth(),
49 | onClick = onCancel,
50 | ) {
51 | Text(text = "Cancel")
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | @Preview
59 | @Composable
60 | fun PreviewToDoCreationDialog() {
61 | AppTheme {
62 | Surface {
63 | ToDoCreationDialog(
64 | onSubmit = {},
65 | onCancel = {},
66 | )
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/ToDoEditingDialog.kt:
--------------------------------------------------------------------------------
1 | package todolist
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material3.Button
9 | import androidx.compose.material3.ButtonDefaults
10 | import androidx.compose.material3.Card
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.OutlinedButton
13 | import androidx.compose.material3.OutlinedTextField
14 | import androidx.compose.material3.Surface
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.getValue
18 | import androidx.compose.runtime.mutableStateOf
19 | import androidx.compose.runtime.remember
20 | import androidx.compose.runtime.setValue
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.unit.dp
23 | import androidx.compose.ui.window.Dialog
24 | import org.jetbrains.compose.ui.tooling.preview.Preview
25 | import ui.theme.AppTheme
26 |
27 | @Composable
28 | fun ToDoEditingDialog(
29 | initialText: String,
30 | onSubmit: (text: String) -> Unit,
31 | onDelete: () -> Unit,
32 | onCancel: () -> Unit,
33 | ) {
34 | var text by remember { mutableStateOf(initialText) }
35 | Dialog(onDismissRequest = {}) {
36 | Card {
37 | Column(modifier = Modifier.padding(16.dp)) {
38 | OutlinedTextField(
39 | modifier = Modifier.fillMaxWidth(),
40 | value = text,
41 | singleLine = true,
42 | onValueChange = { text = it },
43 | )
44 | Spacer(modifier = Modifier.size(16.dp))
45 | Button(
46 | modifier = Modifier.fillMaxWidth(),
47 | onClick = { onSubmit(text) },
48 | ) {
49 | Text(text = "OK")
50 | }
51 | OutlinedButton(
52 | modifier = Modifier.fillMaxWidth(),
53 | onClick = onCancel,
54 | ) {
55 | Text(text = "Cancel")
56 | }
57 | Spacer(modifier = Modifier.size(16.dp))
58 | Button(
59 | modifier = Modifier.fillMaxWidth(),
60 | colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
61 | onClick = { onDelete() },
62 | ) {
63 | Text(text = "Delete")
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
70 | @Preview
71 | @Composable
72 | fun PreviewToDoEditingDialog() {
73 | AppTheme {
74 | Surface {
75 | ToDoEditingDialog(
76 | initialText = "hoge",
77 | onSubmit = {},
78 | onCancel = {},
79 | onDelete = {},
80 | )
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/server/MockServer.kt:
--------------------------------------------------------------------------------
1 | package todolist.server
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 |
5 | val LocalMockServer = compositionLocalOf {
6 | MockServerSucceed
7 | }
8 |
9 | interface MockServer {
10 |
11 | suspend fun getToDoList(): List
12 |
13 | suspend fun addToDo(value: String): List
14 |
15 | suspend fun editToDo(index: Int, value: String): List
16 |
17 | suspend fun removeToDo(index: Int): List
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/server/MockServerAllFailed.kt:
--------------------------------------------------------------------------------
1 | package todolist.server
2 |
3 | import kotlinx.coroutines.delay
4 | import todolist.server.MockServer
5 |
6 | object MockServerAllFailed : MockServer {
7 |
8 | override suspend fun getToDoList(): List {
9 | delay(1000)
10 | throw NullPointerException()
11 | }
12 |
13 | override suspend fun addToDo(value: String): List {
14 | delay(1000)
15 | throw NullPointerException()
16 | }
17 |
18 | override suspend fun editToDo(index: Int, value: String): List {
19 | delay(1000)
20 | throw NullPointerException()
21 | }
22 |
23 | override suspend fun removeToDo(index: Int): List {
24 | delay(1000)
25 | throw NullPointerException()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/server/MockServerLoadingSlow.kt:
--------------------------------------------------------------------------------
1 | package todolist.server
2 |
3 | import kotlinx.coroutines.delay
4 |
5 | object MockServerLoadingSlow : MockServer {
6 |
7 | private var todoList: List = listOf(
8 | "Remember the milk",
9 | "Call bob at 5pm.",
10 | )
11 |
12 | override suspend fun getToDoList(): List {
13 | delay(5000)
14 | return todoList
15 | }
16 |
17 | override suspend fun addToDo(value: String): List {
18 | delay(5000)
19 | todoList = todoList.toList() + value
20 | return todoList
21 | }
22 |
23 | override suspend fun editToDo(index: Int, value: String): List {
24 | delay(5000)
25 | todoList = todoList.toMutableList().apply {
26 | this[index] = value
27 | }
28 | return todoList
29 | }
30 |
31 | override suspend fun removeToDo(index: Int): List {
32 | delay(5000)
33 | todoList = todoList.toMutableList().apply {
34 | removeAt(index)
35 | }
36 | return todoList
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/server/MockServerMutationFailed.kt:
--------------------------------------------------------------------------------
1 | package todolist.server
2 |
3 | import kotlinx.coroutines.delay
4 |
5 | object MockServerMutationFailed : MockServer {
6 |
7 | private var todoList: List = listOf(
8 | "Remember the milk",
9 | "Call bob at 5pm.",
10 | )
11 |
12 | override suspend fun getToDoList(): List {
13 | delay(1000)
14 | return todoList
15 | }
16 |
17 | override suspend fun addToDo(value: String): List {
18 | delay(1000)
19 | throw NullPointerException()
20 | }
21 |
22 | override suspend fun editToDo(index: Int, value: String): List {
23 | delay(1000)
24 | throw NullPointerException()
25 | }
26 |
27 | override suspend fun removeToDo(index: Int): List {
28 | delay(1000)
29 | throw NullPointerException()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/server/MockServerRandomFailed.kt:
--------------------------------------------------------------------------------
1 | package todolist.server
2 |
3 | import kotlinx.coroutines.delay
4 | import kotlin.random.Random
5 |
6 | object MockServerRandomFailed : MockServer {
7 |
8 | private var todoList: List = listOf(
9 | "Remember the milk",
10 | "Call bob at 5pm.",
11 | )
12 |
13 | override suspend fun getToDoList(): List {
14 | delay(1000)
15 | if (Random.nextBoolean()) throw NullPointerException()
16 | return todoList
17 | }
18 |
19 | override suspend fun addToDo(value: String): List {
20 | delay(1000)
21 | if (Random.nextBoolean()) throw NullPointerException()
22 | todoList = todoList.toList() + value
23 | return todoList
24 | }
25 |
26 | override suspend fun editToDo(index: Int, value: String): List {
27 | delay(1000)
28 | if (Random.nextBoolean()) throw NullPointerException()
29 | todoList = todoList.toMutableList().apply {
30 | this[index] = value
31 | }
32 | return todoList
33 | }
34 |
35 | override suspend fun removeToDo(index: Int): List {
36 | delay(1000)
37 | if (Random.nextBoolean()) throw NullPointerException()
38 | todoList = todoList.toMutableList().apply {
39 | removeAt(index)
40 | }
41 | return todoList
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/todolist/server/MockServerSucceed.kt:
--------------------------------------------------------------------------------
1 | package todolist.server
2 |
3 | import kotlinx.coroutines.delay
4 |
5 | object MockServerSucceed : MockServer {
6 |
7 | private var todoList: List = listOf(
8 | "Remember the milk",
9 | "Call bob at 5pm.",
10 | )
11 |
12 | override suspend fun getToDoList(): List {
13 | delay(1000)
14 | return todoList
15 | }
16 |
17 | override suspend fun addToDo(value: String): List {
18 | delay(1000)
19 | todoList = todoList.toList() + value
20 | return todoList
21 | }
22 |
23 | override suspend fun editToDo(index: Int, value: String): List {
24 | delay(1000)
25 | todoList = todoList.toMutableList().apply {
26 | this[index] = value
27 | }
28 | return todoList
29 | }
30 |
31 | override suspend fun removeToDo(index: Int): List {
32 | delay(1000)
33 | todoList = todoList.toMutableList().apply {
34 | removeAt(index)
35 | }
36 | return todoList
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/ui/ErrorContent.kt:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Surface
12 | import androidx.compose.material3.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.text.style.TextAlign
17 | import androidx.compose.ui.unit.dp
18 | import ui.theme.AppTheme
19 | import org.jetbrains.compose.ui.tooling.preview.Preview
20 |
21 | @Composable
22 | fun ErrorContent(
23 | onRetry: (() -> Unit)? = null,
24 | ) {
25 | Column(
26 | modifier = Modifier
27 | .fillMaxSize()
28 | .padding(16.dp),
29 | verticalArrangement = Arrangement.Center,
30 | horizontalAlignment = Alignment.CenterHorizontally,
31 | ) {
32 | Text(
33 | text = "Validation failed.",
34 | textAlign = TextAlign.Center,
35 | style = MaterialTheme.typography.titleSmall,
36 | color = MaterialTheme.colorScheme.error,
37 | )
38 | Spacer(modifier = Modifier.size(4.dp))
39 | if (onRetry != null) {
40 | Button(onClick = onRetry) {
41 | Text("Retry")
42 | }
43 | }
44 | }
45 | }
46 |
47 | @Preview
48 | @Composable
49 | fun PreviewErrorContent() {
50 | AppTheme {
51 | Surface {
52 | ErrorContent {}
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/ui/LoadingContent.kt:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.CircularProgressIndicator
8 | import androidx.compose.material3.Surface
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 | import ui.theme.AppTheme
14 | import org.jetbrains.compose.ui.tooling.preview.Preview
15 |
16 | @Composable
17 | fun LoadingContent() {
18 | Column(
19 | modifier = Modifier
20 | .fillMaxSize()
21 | .padding(16.dp),
22 | verticalArrangement = Arrangement.Center,
23 | horizontalAlignment = Alignment.CenterHorizontally,
24 | ) {
25 | CircularProgressIndicator()
26 | }
27 | }
28 |
29 | @Preview
30 | @Composable
31 | fun PreviewLoadingContent() {
32 | AppTheme {
33 | Surface {
34 | LoadingContent()
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple40 = Color(0xFF6650a4)
6 | val PurpleGrey40 = Color(0xFF625b71)
7 | val Pink40 = Color(0xFF7D5260)
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package ui.theme
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.lightColorScheme
5 | import androidx.compose.runtime.Composable
6 |
7 | private val LightColorScheme = lightColorScheme(
8 | primary = Purple40,
9 | secondary = PurpleGrey40,
10 | tertiary = Pink40,
11 | )
12 |
13 | @Composable
14 | fun AppTheme(
15 | content: @Composable () -> Unit
16 | ) {
17 | MaterialTheme(
18 | colorScheme = LightColorScheme,
19 | typography = Typography,
20 | content = content
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
35 |
--------------------------------------------------------------------------------
/composeApp/src/desktopMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.Window
2 | import androidx.compose.ui.window.application
3 |
4 | fun main() = application {
5 | Window(
6 | onCloseRequest = ::exitApplication,
7 | title = "SWR Compose",
8 | ) {
9 | App()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/MainViewController.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 |
3 | fun MainViewController() = ComposeUIViewController { App() }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | version = "0.7.1"
3 |
4 | compileSdk = "34"
5 | minSdk = "21"
6 | targetSdk = "34"
7 |
8 | kotlin = "2.0.20"
9 | coroutines = "1.9.0"
10 | compose-ui = "1.7.2"
11 | android-plugin = "8.6.1"
12 |
13 | [libraries]
14 | # Kotlinx Coroutines
15 | kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
16 | kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
17 | # Kotlinx DateTime
18 | kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.6.1"
19 | # AndroidX Compose UI Test
20 | androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-ui" }
21 | androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose-ui" }
22 | # AndroidX Activity Compose
23 | androidx-activity-compose = "androidx.activity:activity-compose:1.9.2"
24 | # AndroidX Navigation Compose
25 | androidx-navigation-compose = "org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10"
26 | # AndroidX Lifecycle Runtime
27 | androidx-lifecycle-runtime = "androidx.lifecycle:lifecycle-runtime:2.8.6"
28 | # Konnection
29 | konnection = "dev.tmapps:konnection:1.4.2"
30 | # MockK
31 | mockk = "io.mockk:mockk:1.13.12"
32 | # Robolectric
33 | robolectric = "org.robolectric:robolectric:4.13"
34 |
35 | [plugins]
36 | # Kotlin
37 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
38 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } # https://kotlinlang.org/docs/multiplatform-compatibility-guide.html#version-compatibility
39 | # Compose
40 | compose = { id = "org.jetbrains.compose", version = "1.6.11" }
41 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
42 | # Dokka
43 | dokka = { id = "org.jetbrains.dokka", version = "1.9.20" }
44 | # Android
45 | android-application = { id = "com.android.application", version.ref = "android-plugin" }
46 | android-library = { id = "com.android.library", version.ref = "android-plugin" }
47 | # Nexus Publish
48 | nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" }
49 | # Binary Compatibility Validator
50 | binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.16.3" }
51 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/iosApp/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/xcode
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode
3 |
4 | ### Xcode ###
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## Xcode 8 and earlier
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ### Xcode Patch ###
13 | *.xcodeproj/*
14 | !*.xcodeproj/project.pbxproj
15 | !*.xcodeproj/xcshareddata/
16 | !*.xcodeproj/project.xcworkspace/
17 | !*.xcworkspace/contents.xcworkspacedata
18 | /*.gcno
19 | **/xcshareddata/WorkspaceSettings.xcsettings
20 |
21 | # End of https://www.toptal.com/developers/gitignore/api/xcode
22 |
--------------------------------------------------------------------------------
/iosApp/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=com.kazakago.swr.compose.example
3 | APP_NAME=SWR Compose
4 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app-icon-1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazakago/swr-compose/53ef41c3fcabc4d4a8a650406860a01b24a1d361/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import ComposeApp
4 |
5 | struct ComposeView: UIViewControllerRepresentable {
6 | func makeUIViewController(context: Context) -> UIViewController {
7 | MainViewControllerKt.MainViewController()
8 | }
9 |
10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
11 | }
12 |
13 | struct ContentView: View {
14 | var body: some View {
15 | ComposeView()
16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
17 | }
18 | }
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @main
4 | struct iOSApp: App {
5 | var body: some Scene {
6 | WindowGroup {
7 | ContentView()
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "swr_compose"
17 | include(":composeApp")
18 | include(":swr")
19 |
20 | // https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:type-safe-project-accessors
21 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
22 |
--------------------------------------------------------------------------------
/swr/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/DummyException.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | object DummyException1 : Exception()
4 | object DummyException2 : Exception()
5 | object DummyException3 : Exception()
6 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/NetworkRule.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | import com.kazakago.swr.compose.internal.GlobalKonnection
4 | import dev.tmapps.konnection.Konnection
5 | import io.mockk.every
6 | import io.mockk.mockk
7 | import kotlinx.coroutines.channels.Channel
8 | import kotlinx.coroutines.flow.receiveAsFlow
9 | import org.junit.rules.TestRule
10 | import org.junit.runner.Description
11 | import org.junit.runners.model.Statement
12 |
13 | class NetworkRule : TestRule {
14 |
15 | private val konnection = mockk()
16 | private val connectionObserver = Channel(Channel.CONFLATED)
17 |
18 | override fun apply(base: Statement, description: Description): Statement {
19 | return object : Statement() {
20 | override fun evaluate() {
21 | try {
22 | setup()
23 | base.evaluate()
24 | } finally {
25 | teardown()
26 | }
27 | }
28 | }
29 | }
30 |
31 | private fun setup() {
32 | every { konnection.isConnected() } returns true
33 | every { konnection.observeHasConnection() } returns connectionObserver.receiveAsFlow()
34 | GlobalKonnection = konnection
35 | }
36 |
37 | private fun teardown() {
38 | }
39 |
40 | fun changeNetwork(isConnected: Boolean) {
41 | every { konnection.isConnected() } returns isConnected
42 | connectionObserver.trySend(isConnected)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/config/GlobalConfigutationsTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.config
2 |
3 | import androidx.compose.ui.test.junit4.createComposeRule
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.kazakago.swr.compose.NetworkRule
6 | import com.kazakago.swr.compose.useSWR
7 | import com.kazakago.swr.compose.useSWRConfig
8 | import org.junit.Rule
9 | import org.junit.runner.RunWith
10 | import kotlin.random.Random
11 | import kotlin.test.Test
12 | import kotlin.test.assertEquals
13 | import kotlin.time.Duration.Companion.seconds
14 |
15 | @RunWith(AndroidJUnit4::class)
16 | class GlobalConfigutationsTest {
17 |
18 | @get:Rule
19 | val networkRule = NetworkRule()
20 |
21 | @get:Rule
22 | val composeTestRule = createComposeRule().apply {
23 | mainClock.autoAdvance = false
24 | }
25 |
26 | @Test
27 | fun defaultOptions() {
28 | val key = Random.nextInt().toString()
29 | val globalFetcher: suspend (Any) -> Any = { "${it}_fetched" }
30 | var data: String? = null
31 | composeTestRule.setContent {
32 | val (_, config) = useSWRConfig()
33 | assertEquals(null, config.fetcher)
34 | assertEquals(2.seconds, config.dedupingInterval)
35 | SWRConfig(options = {
36 | fetcher = globalFetcher
37 | dedupingInterval = 10.seconds
38 | }) {
39 | val state = useSWR(key = key) {
40 | @Suppress("UNCHECKED_CAST")
41 | assertEquals(globalFetcher, fetcher as suspend (Any) -> Any)
42 | assertEquals(10.seconds, dedupingInterval)
43 | }
44 | data = state.data
45 | }
46 | }
47 |
48 | composeTestRule.mainClock.advanceTimeBy(1)
49 | assertEquals("${key}_fetched", data)
50 | }
51 |
52 | @Test
53 | fun nestedDefaultOptions() {
54 | val globalFetcher1: suspend (Any) -> Any = { "${it}_fetched_1" }
55 | val globalFetcher2: suspend (Any) -> Any = { "${it}_fetched_2" }
56 | val key1 = "${object {}.javaClass.enclosingMethod?.name}_1"
57 | val key2 = "${object {}.javaClass.enclosingMethod?.name}_2"
58 | val key3 = "${object {}.javaClass.enclosingMethod?.name}_3"
59 | var data1: String? = null
60 | var data2: String? = null
61 | var data3: String? = null
62 | composeTestRule.setContent {
63 | val (_, config) = useSWRConfig()
64 | assertEquals(null, config.fetcher)
65 | assertEquals(2.seconds, config.dedupingInterval)
66 | SWRConfig(options = {
67 | fetcher = globalFetcher1
68 | dedupingInterval = 10.seconds
69 | }) {
70 | val state1 = useSWR(key = key1) {
71 | @Suppress("UNCHECKED_CAST")
72 | assertEquals(globalFetcher1, fetcher as suspend (Any) -> Any)
73 | assertEquals(10.seconds, dedupingInterval)
74 | }
75 | data1 = state1.data
76 |
77 | SWRConfig(options = {
78 | fetcher = globalFetcher2
79 | dedupingInterval = 20.seconds
80 | }) {
81 | val state2 = useSWR(key = key2) {
82 | @Suppress("UNCHECKED_CAST")
83 | assertEquals(globalFetcher2, fetcher as suspend (Any) -> Any)
84 | assertEquals(20.seconds, dedupingInterval)
85 | }
86 | data2 = state2.data
87 | }
88 |
89 | val state3 = useSWR(key = key3) {
90 | @Suppress("UNCHECKED_CAST")
91 | assertEquals(globalFetcher1, fetcher as suspend (Any) -> Any)
92 | assertEquals(10.seconds, dedupingInterval)
93 | }
94 | data3 = state3.data
95 | }
96 | }
97 |
98 | composeTestRule.mainClock.advanceTimeBy(1)
99 | assertEquals("${key1}_fetched_1", data1)
100 | assertEquals("${key2}_fetched_2", data2)
101 | assertEquals("${key3}_fetched_1", data3)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/config/UseSWRConfigTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.config
2 |
3 | import androidx.compose.runtime.LaunchedEffect
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import androidx.compose.ui.test.junit4.createComposeRule
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import com.kazakago.swr.compose.useSWRConfig
12 | import kotlinx.coroutines.delay
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class UseSWRConfigTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun mutation() {
32 | val key = Random.nextInt().toString()
33 | val stateList = mutableListOf>()
34 | composeTestRule.setContent {
35 | SWRGlobalScope = rememberCoroutineScope()
36 | stateList += useSWR(key = key, fetcher = {
37 | delay(100)
38 | "fetched"
39 | }) {
40 | revalidateOnMount = false
41 | }
42 | val (mutate, _, _) = useSWRConfig()
43 | LaunchedEffect(Unit) {
44 | mutate(key = key)
45 | }
46 | }
47 |
48 | composeTestRule.mainClock.advanceTimeBy(1000)
49 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
50 | assertEquals(listOf(null, null, null), stateList.map { it.error })
51 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
52 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/immutable/UseSWRImmutableTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.immutable
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule
6 | import androidx.lifecycle.Lifecycle
7 | import androidx.test.ext.junit.runners.AndroidJUnit4
8 | import com.kazakago.swr.compose.NetworkRule
9 | import com.kazakago.swr.compose.cache.LocalSWRCache
10 | import com.kazakago.swr.compose.internal.SWRGlobalScope
11 | import com.kazakago.swr.compose.state.SWRState
12 | import com.kazakago.swr.compose.useSWRImmutable
13 | import kotlinx.coroutines.delay
14 | import org.junit.Rule
15 | import org.junit.runner.RunWith
16 | import kotlin.random.Random
17 | import kotlin.test.Test
18 | import kotlin.test.assertEquals
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class UseSWRImmutableTest {
22 |
23 | @get:Rule
24 | val networkRule = NetworkRule()
25 |
26 | @get:Rule
27 | val composeTestRule = createAndroidComposeRule().apply {
28 | mainClock.autoAdvance = false
29 | }
30 |
31 | @Test
32 | fun immutable() {
33 | val key = Random.nextInt().toString()
34 | val stateList = mutableListOf>()
35 | composeTestRule.setContent {
36 | SWRGlobalScope = rememberCoroutineScope()
37 | LocalSWRCache.current.state(key = key).let {
38 | if (it.value == null) it.value = "cached"
39 | }
40 | stateList += useSWRImmutable(key = key, fetcher = {
41 | delay(100)
42 | "fetched"
43 | })
44 | }
45 |
46 | composeTestRule.mainClock.advanceTimeBy(5000)
47 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.STARTED)
48 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
49 | composeTestRule.mainClock.advanceTimeBy(5000)
50 |
51 | assertEquals(listOf("cached"), stateList.map { it.data })
52 | assertEquals(listOf(null), stateList.map { it.error })
53 | assertEquals(listOf(false), stateList.map { it.isLoading })
54 | assertEquals(listOf(false), stateList.map { it.isValidating })
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/infinite/InitialSizeOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.infinite
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRInfiniteState
9 | import com.kazakago.swr.compose.useSWRInfinite
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class InitialSizeOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun initialSize1() {
30 | val key = Random.nextInt().toString()
31 | val stateList = mutableListOf>()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | stateList += useSWRInfinite(getKey = { pageIndex, _ -> "${key}_${pageIndex}" }, fetcher = {
35 | delay(100)
36 | "fetched"
37 | }) {
38 | initialSize = 1
39 | }
40 | }
41 |
42 | composeTestRule.mainClock.advanceTimeBy(2500)
43 | assertEquals(listOf(null, null, listOf("fetched")), stateList.map { it.data })
44 | assertEquals(listOf(null, null, null), stateList.map { it.error })
45 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
46 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
47 | }
48 |
49 | @Test
50 | fun initialSize3() {
51 | val key = Random.nextInt().toString()
52 | val stateList = mutableListOf>()
53 | composeTestRule.setContent {
54 | SWRGlobalScope = rememberCoroutineScope()
55 | stateList += useSWRInfinite(getKey = { pageIndex, _ -> "${key}_${pageIndex}" }, fetcher = {
56 | delay(100)
57 | "fetched"
58 | }) {
59 | initialSize = 3
60 | }
61 | }
62 |
63 | composeTestRule.mainClock.advanceTimeBy(2500)
64 | assertEquals(listOf(null, null, listOf("fetched", "fetched", "fetched")), stateList.map { it.data })
65 | assertEquals(listOf(null, null, null), stateList.map { it.error })
66 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
67 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/infinite/UseSWRInfiniteTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.infinite
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRInfiniteState
9 | import com.kazakago.swr.compose.useSWRInfinite
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.delay
12 | import kotlinx.coroutines.launch
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class UseSWRInfiniteTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun validate() {
32 | val key = Random.nextInt().toString()
33 | val stateList = mutableListOf>()
34 | composeTestRule.setContent {
35 | SWRGlobalScope = rememberCoroutineScope()
36 | stateList += useSWRInfinite(getKey = { pageIndex, _ -> "${key}_${pageIndex}" }, fetcher = {
37 | delay(100)
38 | "fetched"
39 | })
40 | }
41 |
42 | composeTestRule.mainClock.advanceTimeBy(2500)
43 | assertEquals(listOf(null, null, listOf("fetched")), stateList.map { it.data })
44 | assertEquals(listOf(null, null, null), stateList.map { it.error })
45 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
46 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
47 | }
48 |
49 | @Test
50 | fun incrementSetSize() {
51 | val key = Random.nextInt().toString()
52 | val stateList = mutableListOf>()
53 | lateinit var scope: CoroutineScope
54 | composeTestRule.setContent {
55 | scope = rememberCoroutineScope()
56 | SWRGlobalScope = rememberCoroutineScope()
57 | stateList += useSWRInfinite(getKey = { pageIndex, _ -> "${key}_${pageIndex}" }, fetcher = {
58 | delay(100)
59 | "fetched_$it"
60 | })
61 | }
62 |
63 | composeTestRule.mainClock.advanceTimeBy(2500)
64 | val (_, _, _, _, _, size, setSize) = stateList.last()
65 | scope.launch { setSize(size + 1) }
66 |
67 | composeTestRule.mainClock.advanceTimeBy(2500)
68 | assertEquals(listOf(null, null, listOf("fetched_${key}_1"), listOf("fetched_${key}_1", null), listOf("fetched_${key}_1", null), listOf("fetched_${key}_1", "fetched_${key}_2")), stateList.map { it.data })
69 | assertEquals(listOf(null, null, null, null, null, null), stateList.map { it.error })
70 | assertEquals(listOf(false, true, false, false, false, false), stateList.map { it.isLoading })
71 | assertEquals(listOf(false, true, false, false, true, false), stateList.map { it.isValidating })
72 | assertEquals(listOf(1, 1, 1, 2, 2, 2), stateList.map { it.size })
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/mutation/OptimisticOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.mutation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.delay
12 | import kotlinx.coroutines.launch
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class OptimisticOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun withOptimisticData() {
32 | val key = Random.nextInt().toString()
33 | var result: () -> String = { "fetched_1" }
34 | val stateList = mutableListOf>()
35 | lateinit var scope: CoroutineScope
36 | composeTestRule.setContent {
37 | scope = rememberCoroutineScope()
38 | SWRGlobalScope = rememberCoroutineScope()
39 | stateList += useSWR(key = key, fetcher = {
40 | delay(100)
41 | result()
42 | })
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(2500)
46 | result = { "fetched_2" }
47 | scope.launch {
48 | stateList.last().mutate(data = {
49 | delay(100)
50 | "mutated"
51 | }) {
52 | optimisticData = "optimisticData"
53 | }
54 | }
55 |
56 | composeTestRule.mainClock.advanceTimeBy(2500)
57 | assertEquals(listOf(null, null, "fetched_1", "optimisticData", "mutated", "fetched_2"), stateList.map { it.data })
58 | assertEquals(listOf(null, null, null, null, null, null), stateList.map { it.error })
59 | assertEquals(listOf(false, true, false, false, false, false), stateList.map { it.isLoading })
60 | assertEquals(listOf(false, true, false, false, true, false), stateList.map { it.isValidating })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/mutation/PopulateCacheOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.mutation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.delay
12 | import kotlinx.coroutines.launch
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class PopulateCacheOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun noPopulateCache() {
32 | val key = Random.nextInt().toString()
33 | var result: () -> String = { "fetched_1" }
34 | val stateList = mutableListOf>()
35 | lateinit var scope: CoroutineScope
36 | composeTestRule.setContent {
37 | scope = rememberCoroutineScope()
38 | SWRGlobalScope = rememberCoroutineScope()
39 | stateList += useSWR(key = key, fetcher = {
40 | delay(100)
41 | result()
42 | })
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(2500)
46 | result = { "fetched_2" }
47 | scope.launch {
48 | stateList.last().mutate(data = {
49 | delay(100)
50 | "mutated"
51 | }) {
52 | populateCache = false
53 | }
54 | }
55 |
56 | composeTestRule.mainClock.advanceTimeBy(2500)
57 | assertEquals(listOf(null, null, "fetched_1", "fetched_1", "fetched_2"), stateList.map { it.data })
58 | assertEquals(listOf(null, null, null, null, null), stateList.map { it.error })
59 | assertEquals(listOf(false, true, false, false, false), stateList.map { it.isLoading })
60 | assertEquals(listOf(false, true, false, true, false), stateList.map { it.isValidating })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/mutation/RevalidateOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.mutation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.delay
12 | import kotlinx.coroutines.launch
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class RevalidateOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun noRevalidate() {
32 | val key = Random.nextInt().toString()
33 | var result: () -> String = { "fetched_1" }
34 | val stateList = mutableListOf>()
35 | lateinit var scope: CoroutineScope
36 | composeTestRule.setContent {
37 | scope = rememberCoroutineScope()
38 | SWRGlobalScope = rememberCoroutineScope()
39 | stateList += useSWR(key = key, fetcher = {
40 | delay(100)
41 | result()
42 | })
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(2500)
46 | result = { "fetched_2" }
47 | scope.launch {
48 | stateList.last().mutate(data = {
49 | delay(100)
50 | "mutated"
51 | }) {
52 | revalidate = false
53 | }
54 | }
55 |
56 | composeTestRule.mainClock.advanceTimeBy(2500)
57 | assertEquals(listOf(null, null, "fetched_1", "mutated"), stateList.map { it.data })
58 | assertEquals(listOf(null, null, null, null), stateList.map { it.error })
59 | assertEquals(listOf(false, true, false, false), stateList.map { it.isLoading })
60 | assertEquals(listOf(false, true, false, false), stateList.map { it.isValidating })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/mutation/RollbackOnErrorOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.mutation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.delay
13 | import kotlinx.coroutines.launch
14 | import org.junit.Rule
15 | import org.junit.runner.RunWith
16 | import kotlin.random.Random
17 | import kotlin.test.Test
18 | import kotlin.test.assertEquals
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class RollbackOnErrorOptionTest {
22 |
23 | @get:Rule
24 | val networkRule = NetworkRule()
25 |
26 | @get:Rule
27 | val composeTestRule = createComposeRule().apply {
28 | mainClock.autoAdvance = false
29 | }
30 |
31 | @Test
32 | fun noRollbackOnError() {
33 | val key = Random.nextInt().toString()
34 | var result: () -> String = { "fetched_1" }
35 | val stateList = mutableListOf>()
36 | lateinit var scope: CoroutineScope
37 | composeTestRule.setContent {
38 | scope = rememberCoroutineScope()
39 | SWRGlobalScope = rememberCoroutineScope()
40 | stateList += useSWR(key = key, fetcher = {
41 | delay(100)
42 | result()
43 | })
44 | }
45 |
46 | composeTestRule.mainClock.advanceTimeBy(2500)
47 | result = { "fetched_2" }
48 | var mutationError: Throwable? = null
49 | scope.launch {
50 | runCatching {
51 | stateList.last().mutate(data = {
52 | delay(100)
53 | throw DummyException1
54 | }) {
55 | optimisticData = "optimisticData"
56 | rollbackOnError = false
57 | }
58 | }.onFailure {
59 | mutationError = it
60 | }
61 | }
62 |
63 | composeTestRule.mainClock.advanceTimeBy(2500)
64 | assertEquals(listOf(null, null, "fetched_1", "optimisticData"), stateList.map { it.data })
65 | assertEquals(listOf(null, null, null, null), stateList.map { it.error })
66 | assertEquals(listOf(false, true, false, false), stateList.map { it.isLoading })
67 | assertEquals(listOf(false, true, false, false), stateList.map { it.isValidating })
68 | assertEquals(DummyException1, mutationError)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/mutation/ThrowOnErrorOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.mutation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.delay
13 | import kotlinx.coroutines.launch
14 | import org.junit.Rule
15 | import org.junit.runner.RunWith
16 | import kotlin.random.Random
17 | import kotlin.test.Test
18 | import kotlin.test.assertEquals
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class ThrowOnErrorOptionTest {
22 |
23 | @get:Rule
24 | val networkRule = NetworkRule()
25 |
26 | @get:Rule
27 | val composeTestRule = createComposeRule().apply {
28 | mainClock.autoAdvance = false
29 | }
30 |
31 | @Test
32 | fun withThrowOnError() {
33 | val key = Random.nextInt().toString()
34 | val stateList = mutableListOf>()
35 | lateinit var scope: CoroutineScope
36 | composeTestRule.setContent {
37 | scope = rememberCoroutineScope()
38 | SWRGlobalScope = rememberCoroutineScope()
39 | stateList += useSWR(key = key, fetcher = {
40 | delay(100)
41 | "fetched"
42 | })
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(2500)
46 | var mutationError: Throwable? = null
47 | scope.launch {
48 | runCatching {
49 | stateList.last().mutate(data = {
50 | delay(100)
51 | throw DummyException1
52 | }) {
53 | throwOnError = true
54 | }
55 | }.onFailure {
56 | mutationError = it
57 | }
58 | }
59 |
60 | composeTestRule.mainClock.advanceTimeBy(2500)
61 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
62 | assertEquals(listOf(null, null, null), stateList.map { it.error })
63 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
64 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
65 | assertEquals(DummyException1, mutationError)
66 | }
67 |
68 | @Test
69 | fun noThrowOnError() {
70 | val key = Random.nextInt().toString()
71 | val stateList = mutableListOf>()
72 | lateinit var scope: CoroutineScope
73 | composeTestRule.setContent {
74 | scope = rememberCoroutineScope()
75 | SWRGlobalScope = rememberCoroutineScope()
76 | stateList += useSWR(key = key, fetcher = {
77 | delay(100)
78 | "fetched"
79 | })
80 | }
81 |
82 | composeTestRule.mainClock.advanceTimeBy(2500)
83 | var mutationError: Throwable? = null
84 | scope.launch {
85 | runCatching {
86 | stateList.last().mutate(data = {
87 | delay(100)
88 | throw DummyException1
89 | }) {
90 | throwOnError = false
91 | }
92 | }.onFailure {
93 | mutationError = it
94 | }
95 | }
96 |
97 | composeTestRule.mainClock.advanceTimeBy(2500)
98 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
99 | assertEquals(listOf(null, null, null), stateList.map { it.error })
100 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
101 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
102 | assertEquals(null, mutationError)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/prefetch/UsePreloadTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.prefetch
2 |
3 | import androidx.compose.runtime.LaunchedEffect
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import androidx.compose.ui.test.junit4.createComposeRule
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import com.kazakago.swr.compose.DummyException1
8 | import com.kazakago.swr.compose.NetworkRule
9 | import com.kazakago.swr.compose.internal.SWRGlobalScope
10 | import com.kazakago.swr.compose.state.SWRState
11 | import com.kazakago.swr.compose.useSWR
12 | import com.kazakago.swr.compose.useSWRPreload
13 | import kotlinx.coroutines.delay
14 | import org.junit.Rule
15 | import org.junit.runner.RunWith
16 | import kotlin.random.Random
17 | import kotlin.test.Test
18 | import kotlin.test.assertEquals
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class UsePreloadTest {
22 |
23 | @get:Rule
24 | val networkRule = NetworkRule()
25 |
26 | @get:Rule
27 | val composeTestRule = createComposeRule().apply {
28 | mainClock.autoAdvance = false
29 | }
30 |
31 | @Test
32 | fun prefetch() {
33 | val key = Random.nextInt().toString()
34 | val stateList = mutableListOf>()
35 | composeTestRule.setContent {
36 | SWRGlobalScope = rememberCoroutineScope()
37 | stateList += useSWR(key = key) {
38 | revalidateOnMount = false
39 | }
40 | val preload = useSWRPreload(key = key, fetcher = {
41 | delay(100)
42 | "fetched"
43 | })
44 | LaunchedEffect(Unit) {
45 | preload()
46 | }
47 | }
48 |
49 | composeTestRule.mainClock.advanceTimeBy(1000)
50 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
51 | assertEquals(listOf(null, null, null), stateList.map { it.error })
52 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
53 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
54 | }
55 |
56 | @Test
57 | fun prefetchFailed() {
58 | val key = Random.nextInt().toString()
59 | val stateList = mutableListOf>()
60 | composeTestRule.setContent {
61 | SWRGlobalScope = rememberCoroutineScope()
62 | stateList += useSWR(key = key) {
63 | revalidateOnMount = false
64 | }
65 | val preload = useSWRPreload(key = key, fetcher = {
66 | delay(100)
67 | throw DummyException1
68 | })
69 | LaunchedEffect(Unit) {
70 | preload()
71 | }
72 | }
73 |
74 | composeTestRule.mainClock.advanceTimeBy(1000)
75 | assertEquals(listOf(null, null, null), stateList.map { it.data })
76 | assertEquals(listOf(null, null, DummyException1), stateList.map { it.error })
77 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
78 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/trigger/OptimisticOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.trigger
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRMutationState
9 | import com.kazakago.swr.compose.useSWRMutation
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.delay
12 | import kotlinx.coroutines.launch
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class OptimisticOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun trigger_withOptimisticData() {
32 | val key = Random.nextInt().toString()
33 | val stateList = mutableListOf>()
34 | lateinit var scope: CoroutineScope
35 | composeTestRule.setContent {
36 | scope = rememberCoroutineScope()
37 | SWRGlobalScope = rememberCoroutineScope()
38 | stateList += useSWRMutation(key = key, fetcher = { _, arg ->
39 | delay(100)
40 | arg
41 | }) {
42 | optimisticData = "optimisticData"
43 | }
44 | }
45 |
46 | composeTestRule.mainClock.advanceTimeBy(2500)
47 | assertEquals(listOf(null), stateList.map { it.data })
48 | assertEquals(listOf(null), stateList.map { it.error })
49 | assertEquals(listOf(false), stateList.map { it.isMutating })
50 | var triggerResult: String? = null
51 | var mutationError: Throwable? = null
52 | scope.launch {
53 | runCatching {
54 | stateList.last().trigger("trigger")
55 | }.onSuccess {
56 | triggerResult = it
57 | }.onFailure {
58 | mutationError = it
59 | }
60 | }
61 |
62 | composeTestRule.mainClock.advanceTimeBy(2500)
63 | assertEquals(listOf(null, "optimisticData", "trigger"), stateList.map { it.data })
64 | assertEquals(listOf(null, null, null), stateList.map { it.error })
65 | assertEquals(listOf(false, true, false), stateList.map { it.isMutating })
66 | assertEquals("trigger", triggerResult)
67 | assertEquals(null, mutationError)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/ErrorRetryCountOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class ErrorRetryCountOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun errorRetryCountNull() {
30 | val key = Random.nextInt().toString()
31 | val errorRetryCountList = mutableListOf()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | useSWR(key = key, fetcher = {
35 | delay(100)
36 | throw DummyException1
37 | }) {
38 | errorRetryCount = null
39 | onErrorRetry = { _, _, config, _, _ ->
40 | errorRetryCountList += config.errorRetryCount
41 | }
42 | }
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(1000)
46 | assertEquals(listOf(null), errorRetryCountList)
47 | }
48 |
49 | @Test
50 | fun errorRetryCount3() {
51 | val key = Random.nextInt().toString()
52 | val errorRetryCountList = mutableListOf()
53 | composeTestRule.setContent {
54 | SWRGlobalScope = rememberCoroutineScope()
55 | useSWR(key = key, fetcher = {
56 | delay(100)
57 | throw DummyException1
58 | }) {
59 | errorRetryCount = 3
60 | onErrorRetry = { _, _, config, _, _ ->
61 | errorRetryCountList += config.errorRetryCount
62 | }
63 | }
64 | }
65 |
66 | composeTestRule.mainClock.advanceTimeBy(1000)
67 | assertEquals(listOf(3), errorRetryCountList)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/ErrorRetryIntervalOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 | import kotlin.time.Duration
17 | import kotlin.time.Duration.Companion.seconds
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class ErrorRetryIntervalOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun errorRetryIntervalOption5seconds() {
32 | val key = Random.nextInt().toString()
33 | val errorRetryIntervalList = mutableListOf()
34 | composeTestRule.setContent {
35 | SWRGlobalScope = rememberCoroutineScope()
36 | useSWR(key = key, fetcher = {
37 | delay(100)
38 | throw DummyException1
39 | }) {
40 | errorRetryInterval = 5.seconds
41 | onErrorRetry = { _, _, config, _, _ ->
42 | errorRetryIntervalList += config.errorRetryInterval
43 | }
44 | }
45 | }
46 |
47 | composeTestRule.mainClock.advanceTimeBy(1000)
48 | assertEquals(listOf(5.seconds), errorRetryIntervalList)
49 | }
50 |
51 | @Test
52 | fun errorRetryIntervalOption10seconds() {
53 | val key = Random.nextInt().toString()
54 | val errorRetryIntervalList = mutableListOf()
55 | composeTestRule.setContent {
56 | SWRGlobalScope = rememberCoroutineScope()
57 | useSWR(key = key, fetcher = {
58 | delay(100)
59 | throw DummyException1
60 | }) {
61 | errorRetryInterval = 10.seconds
62 | onErrorRetry = { _, _, config, _, _ ->
63 | errorRetryIntervalList += config.errorRetryInterval
64 | }
65 | }
66 | }
67 |
68 | composeTestRule.mainClock.advanceTimeBy(1000)
69 | assertEquals(listOf(10.seconds), errorRetryIntervalList)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/FallbackDataOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class FallbackDataOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun noFallbackData() {
30 | val key = Random.nextInt().toString()
31 | val stateList = mutableListOf>()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | stateList += useSWR(key = key, fetcher = {
35 | delay(100)
36 | "fetched"
37 | }) {
38 | fallbackData = null
39 | }
40 | }
41 |
42 | composeTestRule.mainClock.advanceTimeBy(1000)
43 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
44 | assertEquals(listOf(null, null, null), stateList.map { it.error })
45 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
46 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
47 | }
48 |
49 | @Test
50 | fun withFallbackData() {
51 | val key = Random.nextInt().toString()
52 | val stateList = mutableListOf>()
53 | composeTestRule.setContent {
54 | SWRGlobalScope = rememberCoroutineScope()
55 | stateList += useSWR(key = key, fetcher = {
56 | delay(100)
57 | "fetched"
58 | }) {
59 | fallbackData = "fallback"
60 | }
61 | }
62 |
63 | composeTestRule.mainClock.advanceTimeBy(1000)
64 | assertEquals(listOf("fallback"), stateList.map { it.data })
65 | assertEquals(listOf(null), stateList.map { it.error })
66 | assertEquals(listOf(false), stateList.map { it.isLoading })
67 | assertEquals(listOf(false), stateList.map { it.isValidating })
68 | }
69 |
70 | @Test
71 | fun withFallbackDataAndRevalidateOnMount() {
72 | val key = Random.nextInt().toString()
73 | val stateList = mutableListOf>()
74 | composeTestRule.setContent {
75 | SWRGlobalScope = rememberCoroutineScope()
76 | stateList += useSWR(key = key, fetcher = {
77 | delay(100)
78 | "fetched"
79 | }) {
80 | fallbackData = "fallback"
81 | revalidateOnMount = true
82 | }
83 | }
84 |
85 | composeTestRule.mainClock.advanceTimeBy(1000)
86 | assertEquals(listOf("fallback", "fallback", "fetched"), stateList.map { it.data })
87 | assertEquals(listOf(null, null, null), stateList.map { it.error })
88 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
89 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/FallbackOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.config.SWRConfig
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.delay
12 | import org.junit.Rule
13 | import org.junit.runner.RunWith
14 | import kotlin.random.Random
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class FallbackOptionTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun noFallback() {
31 | val key = Random.nextInt().toString()
32 | val stateList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | SWRConfig(options = {
36 | fallback = emptyMap()
37 | }) {
38 | stateList += useSWR(key = key, fetcher = {
39 | delay(100)
40 | "fetched"
41 | })
42 | }
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(1000)
46 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
47 | assertEquals(listOf(null, null, null), stateList.map { it.error })
48 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
49 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
50 | }
51 |
52 | @Test
53 | fun withFallback() {
54 | val key = Random.nextInt().toString()
55 | val stateList = mutableListOf>()
56 | composeTestRule.setContent {
57 | SWRGlobalScope = rememberCoroutineScope()
58 | SWRConfig(options = {
59 | fallback = mapOf(key to "fallback")
60 | }) {
61 | stateList += useSWR(key = key, fetcher = {
62 | delay(100)
63 | "fetched"
64 | })
65 | }
66 | }
67 |
68 | composeTestRule.mainClock.advanceTimeBy(1000)
69 | assertEquals(listOf("fallback"), stateList.map { it.data })
70 | assertEquals(listOf(null), stateList.map { it.error })
71 | assertEquals(listOf(false), stateList.map { it.isLoading })
72 | assertEquals(listOf(false), stateList.map { it.isValidating })
73 | }
74 |
75 | @Test
76 | fun withFallbackAndRevalidateOnMount() {
77 | val key = Random.nextInt().toString()
78 | val stateList = mutableListOf>()
79 | composeTestRule.setContent {
80 | SWRGlobalScope = rememberCoroutineScope()
81 | SWRConfig(options = {
82 | fallback = mapOf(key to "fallback")
83 | }) {
84 | stateList += useSWR(key = key, fetcher = {
85 | delay(100)
86 | "fetched"
87 | }) {
88 | revalidateOnMount = true
89 | }
90 | }
91 | }
92 |
93 | composeTestRule.mainClock.advanceTimeBy(1000)
94 | assertEquals(listOf("fallback", "fallback", "fetched"), stateList.map { it.data })
95 | assertEquals(listOf(null, null, null), stateList.map { it.error })
96 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
97 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/FetcherOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.config.SWRConfig
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.delay
12 | import org.junit.Rule
13 | import org.junit.runner.RunWith
14 | import kotlin.random.Random
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class FetcherOptionTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun localFetcher() {
31 | val key = Random.nextInt().toString()
32 | val stateList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | stateList += useSWR(key = key) {
36 | fetcher = {
37 | delay(100)
38 | "fetched"
39 | }
40 | }
41 | }
42 |
43 | composeTestRule.mainClock.advanceTimeBy(2500)
44 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
45 | assertEquals(listOf(null, null, null), stateList.map { it.error })
46 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
47 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
48 | }
49 |
50 | @Test
51 | fun savedFetcher() {
52 | val key = Random.nextInt().toString()
53 | val stateList = mutableListOf>()
54 | val stateList2 = mutableListOf>()
55 | composeTestRule.setContent {
56 | SWRGlobalScope = rememberCoroutineScope()
57 | stateList += useSWR(key = key, fetcher = {
58 | delay(100)
59 | "fetched"
60 | })
61 | stateList2 += useSWR(key = key)
62 | }
63 |
64 | composeTestRule.mainClock.advanceTimeBy(2500)
65 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
66 | assertEquals(listOf(null, null, null), stateList.map { it.error })
67 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
68 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
69 |
70 | assertEquals(listOf(null, null, "fetched"), stateList2.map { it.data })
71 | assertEquals(listOf(null, null, null), stateList2.map { it.error })
72 | assertEquals(listOf(false, true, false), stateList2.map { it.isLoading })
73 | assertEquals(listOf(false, true, false), stateList2.map { it.isValidating })
74 | }
75 |
76 | @Test
77 | fun globalFetcher() {
78 | val key = Random.nextInt().toString()
79 | val stateList = mutableListOf>()
80 | composeTestRule.setContent {
81 | SWRGlobalScope = rememberCoroutineScope()
82 | SWRConfig(
83 | options = {
84 | fetcher = {
85 | delay(100)
86 | "fetched"
87 | }
88 | }
89 | ) {
90 | stateList += useSWR(key = key)
91 | }
92 | }
93 | composeTestRule.mainClock.advanceTimeBy(2500)
94 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
95 | assertEquals(listOf(null, null, null), stateList.map { it.error })
96 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
97 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/IsPausedOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.delay
13 | import kotlinx.coroutines.launch
14 | import org.junit.Rule
15 | import org.junit.runner.RunWith
16 | import kotlin.random.Random
17 | import kotlin.test.Test
18 | import kotlin.test.assertEquals
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class IsPausedOptionTest {
22 |
23 | @get:Rule
24 | val networkRule = NetworkRule()
25 |
26 | @get:Rule
27 | val composeTestRule = createComposeRule().apply {
28 | mainClock.autoAdvance = false
29 | }
30 |
31 | @Test
32 | fun withIsPaused() {
33 | val key = Random.nextInt().toString()
34 | val stateList = mutableListOf>()
35 | lateinit var scope: CoroutineScope
36 | composeTestRule.setContent {
37 | scope = rememberCoroutineScope()
38 | SWRGlobalScope = rememberCoroutineScope()
39 | stateList += useSWR(key = key, fetcher = {
40 | delay(100)
41 | throw DummyException1
42 | }) {
43 | isPaused = { true }
44 | }
45 | }
46 |
47 | composeTestRule.mainClock.advanceTimeBy(2500)
48 | scope.launch { stateList.last().mutate() }
49 |
50 | composeTestRule.mainClock.advanceTimeBy(2500)
51 | assertEquals(listOf(null), stateList.map { it.data })
52 | assertEquals(listOf(null), stateList.map { it.error })
53 | assertEquals(listOf(false), stateList.map { it.isLoading })
54 | assertEquals(listOf(false), stateList.map { it.isValidating })
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/LoadingTimeoutOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.useSWR
9 | import kotlinx.coroutines.delay
10 | import org.junit.Rule
11 | import org.junit.runner.RunWith
12 | import kotlin.random.Random
13 | import kotlin.test.Test
14 | import kotlin.test.assertEquals
15 | import kotlin.time.Duration.Companion.seconds
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class LoadingTimeoutOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun focusThrottleInterval3Seconds() {
30 | val key = Random.nextInt().toString()
31 | val keyList = mutableListOf()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | useSWR(key = key, fetcher = {
35 | delay(10.seconds)
36 | "fetched"
37 | }) {
38 | loadingTimeout = 3.seconds
39 | onLoadingSlow = { key, _ ->
40 | keyList.add(key)
41 | }
42 | }
43 | }
44 |
45 | composeTestRule.mainClock.advanceTimeBy(2000)
46 | assertEquals(emptyList(), keyList)
47 | composeTestRule.mainClock.advanceTimeBy(2000)
48 | assertEquals(listOf(key), keyList)
49 | }
50 |
51 | @Test
52 | fun focusThrottleInterval5Seconds() {
53 | val key = Random.nextInt().toString()
54 | val keyList = mutableListOf()
55 | composeTestRule.setContent {
56 | SWRGlobalScope = rememberCoroutineScope()
57 | useSWR(key = key, fetcher = {
58 | delay(10.seconds)
59 | "fetched"
60 | }) {
61 | loadingTimeout = 5.seconds
62 | onLoadingSlow = { key, _ ->
63 | keyList.add(key)
64 | }
65 | }
66 | }
67 |
68 | composeTestRule.mainClock.advanceTimeBy(2000)
69 | assertEquals(emptyList(), keyList)
70 | composeTestRule.mainClock.advanceTimeBy(2000)
71 | assertEquals(emptyList(), keyList)
72 | }
73 |
74 | @Test
75 | fun focusThrottleInterval0Seconds() {
76 | val key = Random.nextInt().toString()
77 | val keyList = mutableListOf()
78 | composeTestRule.setContent {
79 | SWRGlobalScope = rememberCoroutineScope()
80 | useSWR(key = key, fetcher = {
81 | delay(10.seconds)
82 | "fetched"
83 | }) {
84 | loadingTimeout = 0.seconds
85 | onLoadingSlow = { key, _ ->
86 | keyList.add(key)
87 | }
88 | }
89 | }
90 |
91 | composeTestRule.mainClock.advanceTimeBy(2000)
92 | assertEquals(listOf(key), keyList)
93 | composeTestRule.mainClock.advanceTimeBy(2000)
94 | assertEquals(listOf(key), keyList)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/OnErrorOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 | import kotlin.test.fail
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class OnErrorOptionTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun withOnError() {
31 | val key = Random.nextInt().toString()
32 | val onErrorList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | useSWR(key = key, fetcher = {
36 | delay(100)
37 | throw DummyException1
38 | }) {
39 | onSuccess = { _, _, _ ->
40 | fail("Must not reach here")
41 | }
42 | onError = { error, key, _ ->
43 | onErrorList += error to key
44 | }
45 | }
46 | }
47 |
48 | composeTestRule.mainClock.advanceTimeBy(3000)
49 | assertEquals(listOf>(DummyException1 to key), onErrorList)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/OnErrorRetryOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.DummyException2
8 | import com.kazakago.swr.compose.NetworkRule
9 | import com.kazakago.swr.compose.internal.SWRGlobalScope
10 | import com.kazakago.swr.compose.state.SWRState
11 | import com.kazakago.swr.compose.useSWR
12 | import kotlinx.coroutines.delay
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class OnErrorRetryOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun defaultOnErrorRetry() {
32 | val key = Random.nextInt().toString()
33 | var result: () -> String = { throw DummyException1 }
34 | val stateList = mutableListOf>()
35 | composeTestRule.setContent {
36 | SWRGlobalScope = rememberCoroutineScope()
37 | stateList += useSWR(key = key, fetcher = {
38 | delay(100)
39 | result()
40 | })
41 | }
42 |
43 | composeTestRule.mainClock.advanceTimeBy(100)
44 | result = { throw DummyException2 }
45 |
46 | composeTestRule.mainClock.advanceTimeBy(15000)
47 | assertEquals(listOf(null, null, null, null, null), stateList.map { it.data })
48 | assertEquals(listOf(null, null, DummyException1, DummyException1, DummyException2), stateList.map { it.error })
49 | assertEquals(listOf(false, true, false, false, false), stateList.map { it.isLoading })
50 | assertEquals(listOf(false, true, false, true, false), stateList.map { it.isValidating })
51 | }
52 |
53 | @Test
54 | fun customOnErrorRetry() {
55 | val key = Random.nextInt().toString()
56 | var result: () -> String = { throw DummyException1 }
57 | val stateList = mutableListOf>()
58 | composeTestRule.setContent {
59 | SWRGlobalScope = rememberCoroutineScope()
60 | stateList += useSWR(key = key, fetcher = {
61 | delay(100)
62 | result()
63 | }) {
64 | onErrorRetry = { _, key, _, revalidate, options ->
65 | delay(100)
66 | revalidate(key, options)
67 | }
68 | }
69 | }
70 |
71 | composeTestRule.mainClock.advanceTimeBy(100)
72 | result = { throw DummyException2 }
73 |
74 | composeTestRule.mainClock.advanceTimeBy(600)
75 | assertEquals(listOf(null, null, null, null, null, null, null, null, null), stateList.map { it.data })
76 | assertEquals(listOf(null, null, DummyException1, DummyException1, DummyException2, DummyException2, DummyException2, DummyException2, DummyException2), stateList.map { it.error })
77 | assertEquals(listOf(false, true, false, false, false, false, false, false, false), stateList.map { it.isLoading })
78 | assertEquals(listOf(false, true, false, true, false, true, false, true, false), stateList.map { it.isValidating })
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/OnLoadingSlowOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.useSWR
9 | import kotlinx.coroutines.delay
10 | import org.junit.Rule
11 | import org.junit.runner.RunWith
12 | import kotlin.random.Random
13 | import kotlin.test.Test
14 | import kotlin.test.assertEquals
15 | import kotlin.time.Duration.Companion.seconds
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class OnLoadingSlowOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun onLoadingSlow() {
30 | val key = Random.nextInt().toString()
31 | val keyList = mutableListOf()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | useSWR(key = key, fetcher = {
35 | delay(5.seconds)
36 | "fetched"
37 | }) {
38 | onLoadingSlow = { key, _ ->
39 | keyList.add(key)
40 | }
41 | }
42 | }
43 |
44 | composeTestRule.mainClock.advanceTimeBy(5000)
45 | assertEquals(listOf(key), keyList)
46 | }
47 |
48 | @Test
49 | fun onLoadingSlow2() {
50 | val key = Random.nextInt().toString()
51 | val keyList = mutableListOf()
52 | composeTestRule.setContent {
53 | SWRGlobalScope = rememberCoroutineScope()
54 | useSWR(key = key, fetcher = {
55 | delay(2.seconds)
56 | "fetched"
57 | }) {
58 | onLoadingSlow = { key, _ ->
59 | keyList.add(key)
60 | }
61 | }
62 | }
63 |
64 | composeTestRule.mainClock.advanceTimeBy(5000)
65 | assertEquals(emptyList(), keyList)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/OnSuccessOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.useSWR
9 | import kotlinx.coroutines.delay
10 | import org.junit.Rule
11 | import org.junit.runner.RunWith
12 | import kotlin.random.Random
13 | import kotlin.test.Test
14 | import kotlin.test.assertEquals
15 | import kotlin.test.fail
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class OnSuccessOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun withOnSuccess() {
30 | val key = Random.nextInt().toString()
31 | val onSuccessList = mutableListOf>()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | useSWR(key = key, fetcher = {
35 | delay(100)
36 | "fetched"
37 | }) {
38 | onSuccess = { data, key, _ ->
39 | onSuccessList += data to key
40 | }
41 | onError = { _, _, _ ->
42 | fail("Must not reach here")
43 | }
44 | }
45 | }
46 |
47 | composeTestRule.mainClock.advanceTimeBy(3000)
48 | assertEquals(listOf("fetched" to key), onSuccessList)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RefreshIntervalOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 | import kotlin.time.Duration
17 | import kotlin.time.Duration.Companion.seconds
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | class RefreshIntervalOptionTest {
21 |
22 | @get:Rule
23 | val networkRule = NetworkRule()
24 |
25 | @get:Rule
26 | val composeTestRule = createComposeRule().apply {
27 | mainClock.autoAdvance = false
28 | }
29 |
30 | @Test
31 | fun noRefreshInterval() {
32 | val key = Random.nextInt().toString()
33 | val stateList = mutableListOf>()
34 | composeTestRule.setContent {
35 | SWRGlobalScope = rememberCoroutineScope()
36 | stateList += useSWR(key = key, fetcher = {
37 | delay(100)
38 | "fetched"
39 | }) {
40 | refreshInterval = Duration.ZERO
41 | }
42 | }
43 |
44 | composeTestRule.mainClock.advanceTimeBy(35000)
45 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
46 | assertEquals(listOf(null, null, null), stateList.map { it.error })
47 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
48 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
49 | }
50 |
51 | @Test
52 | fun withRefreshInterval() {
53 | val key = Random.nextInt().toString()
54 | val stateList = mutableListOf>()
55 | composeTestRule.setContent {
56 | SWRGlobalScope = rememberCoroutineScope()
57 | stateList += useSWR(key = key, fetcher = {
58 | delay(100)
59 | "fetched"
60 | }) {
61 | refreshInterval = 10.seconds
62 | }
63 | }
64 |
65 | composeTestRule.mainClock.advanceTimeBy(35000)
66 | assertEquals(listOf(null, null, "fetched", "fetched", "fetched", "fetched", "fetched", "fetched", "fetched"), stateList.map { it.data })
67 | assertEquals(listOf(null, null, null, null, null, null, null, null, null), stateList.map { it.error })
68 | assertEquals(listOf(false, true, false, false, false, false, false, false, false), stateList.map { it.isLoading })
69 | assertEquals(listOf(false, true, false, true, false, true, false, true, false), stateList.map { it.isValidating })
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RefreshWhenHiddenOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule
6 | import androidx.lifecycle.Lifecycle
7 | import androidx.test.ext.junit.runners.AndroidJUnit4
8 | import com.kazakago.swr.compose.NetworkRule
9 | import com.kazakago.swr.compose.internal.SWRGlobalScope
10 | import com.kazakago.swr.compose.state.SWRState
11 | import com.kazakago.swr.compose.useSWR
12 | import kotlinx.coroutines.delay
13 | import org.junit.Rule
14 | import org.junit.runner.RunWith
15 | import kotlin.random.Random
16 | import kotlin.test.Test
17 | import kotlin.test.assertEquals
18 | import kotlin.time.Duration.Companion.seconds
19 |
20 | @RunWith(AndroidJUnit4::class)
21 | class RefreshWhenHiddenOptionTest {
22 |
23 | @get:Rule
24 | val networkRule = NetworkRule()
25 |
26 | @get:Rule
27 | val composeTestRule = createAndroidComposeRule().apply {
28 | mainClock.autoAdvance = false
29 | }
30 |
31 | @Test
32 | fun noRefreshWhenHidden() {
33 | val key = Random.nextInt().toString()
34 | val stateList = mutableListOf>()
35 | composeTestRule.setContent {
36 | SWRGlobalScope = rememberCoroutineScope()
37 | stateList += useSWR(key = key, fetcher = {
38 | delay(100)
39 | "fetched"
40 | }) {
41 | refreshInterval = 10.seconds
42 | refreshWhenHidden = false
43 | }
44 | }
45 |
46 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
47 |
48 | composeTestRule.mainClock.advanceTimeBy(35000)
49 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
50 | assertEquals(listOf(null, null, null), stateList.map { it.error })
51 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
52 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
53 | }
54 |
55 | @Test
56 | fun withRefreshWhenHidden() {
57 | val key = Random.nextInt().toString()
58 | val stateList = mutableListOf>()
59 | composeTestRule.setContent {
60 | SWRGlobalScope = rememberCoroutineScope()
61 | stateList += useSWR(key = key, fetcher = {
62 | delay(100)
63 | "fetched"
64 | }) {
65 | refreshInterval = 10.seconds
66 | refreshWhenHidden = true
67 | }
68 | }
69 |
70 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
71 |
72 | composeTestRule.mainClock.advanceTimeBy(35000)
73 | assertEquals(listOf(null, null, "fetched", "fetched", "fetched", "fetched", "fetched", "fetched", "fetched"), stateList.map { it.data })
74 | assertEquals(listOf(null, null, null, null, null, null, null, null, null), stateList.map { it.error })
75 | assertEquals(listOf(false, true, false, false, false, false, false, false, false), stateList.map { it.isLoading })
76 | assertEquals(listOf(false, true, false, true, false, true, false, true, false), stateList.map { it.isValidating })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RefreshWhenOfflineOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 | import kotlin.time.Duration.Companion.seconds
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class RefreshWhenOfflineOptionTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun noRefreshWhenOffline() {
31 | val key = Random.nextInt().toString()
32 | val stateList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | stateList += useSWR(key = key, fetcher = {
36 | delay(100)
37 | "fetched"
38 | }) {
39 | refreshInterval = 10.seconds
40 | refreshWhenOffline = false
41 | }
42 | }
43 |
44 | networkRule.changeNetwork(isConnected = false)
45 |
46 | composeTestRule.mainClock.advanceTimeBy(35000)
47 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
48 | assertEquals(listOf(null, null, null), stateList.map { it.error })
49 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
50 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
51 | }
52 |
53 | @Test
54 | fun withRefreshWhenOffline() {
55 | val key = Random.nextInt().toString()
56 | val stateList = mutableListOf>()
57 | composeTestRule.setContent {
58 | SWRGlobalScope = rememberCoroutineScope()
59 | stateList += useSWR(key = key, fetcher = {
60 | delay(100)
61 | "fetched"
62 | }) {
63 | refreshInterval = 10.seconds
64 | refreshWhenOffline = true
65 | }
66 | }
67 |
68 | networkRule.changeNetwork(isConnected = false)
69 |
70 | composeTestRule.mainClock.advanceTimeBy(35000)
71 | assertEquals(listOf(null, null, "fetched", "fetched", "fetched", "fetched", "fetched", "fetched", "fetched"), stateList.map { it.data })
72 | assertEquals(listOf(null, null, null, null, null, null, null, null, null), stateList.map { it.error })
73 | assertEquals(listOf(false, true, false, false, false, false, false, false, false), stateList.map { it.isLoading })
74 | assertEquals(listOf(false, true, false, true, false, true, false, true, false), stateList.map { it.isValidating })
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RevalidateIfStaleOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.cache.LocalSWRCache
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.delay
12 | import org.junit.Rule
13 | import org.junit.runner.RunWith
14 | import kotlin.random.Random
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class RevalidateIfStaleOptionTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun withRevalidateIfStale() {
31 | val key = Random.nextInt().toString()
32 | val stateList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | LocalSWRCache.current.state(key = key).let {
36 | if (it.value == null) it.value = "cached"
37 | }
38 | stateList += useSWR(key = key, fetcher = {
39 | delay(100)
40 | "fetched"
41 | }) {
42 | revalidateIfStale = true
43 | }
44 | }
45 |
46 | composeTestRule.mainClock.advanceTimeBy(2500)
47 | assertEquals(listOf("cached", "cached", "fetched"), stateList.map { it.data })
48 | assertEquals(listOf(null, null, null), stateList.map { it.error })
49 | assertEquals(listOf(false, false, false), stateList.map { it.isLoading })
50 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
51 | }
52 |
53 | @Test
54 | fun noRevalidateIfStale() {
55 | val key = Random.nextInt().toString()
56 | val stateList = mutableListOf>()
57 | composeTestRule.setContent {
58 | SWRGlobalScope = rememberCoroutineScope()
59 | LocalSWRCache.current.state(key = key).let {
60 | if (it.value == null) it.value = "cached"
61 | }
62 | stateList += useSWR(key = key, fetcher = {
63 | delay(100)
64 | "fetched"
65 | }) {
66 | revalidateIfStale = false
67 | }
68 | }
69 |
70 | composeTestRule.mainClock.advanceTimeBy(2500)
71 | assertEquals(listOf("cached"), stateList.map { it.data })
72 | assertEquals(listOf(null), stateList.map { it.error })
73 | assertEquals(listOf(false), stateList.map { it.isLoading })
74 | assertEquals(listOf(false), stateList.map { it.isValidating })
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RevalidateOnFocusOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule
6 | import androidx.lifecycle.Lifecycle
7 | import androidx.test.ext.junit.runners.AndroidJUnit4
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.delay
12 | import org.junit.Rule
13 | import org.junit.runner.RunWith
14 | import kotlin.random.Random
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class RevalidateOnFocusOptionTest {
20 |
21 | @get:Rule
22 | val composeTestRule = createAndroidComposeRule().apply {
23 | mainClock.autoAdvance = false
24 | }
25 |
26 | @Test
27 | fun withRevalidateOnFocus() {
28 | val key = Random.nextInt().toString()
29 | val stateList = mutableListOf>()
30 | composeTestRule.setContent {
31 | SWRGlobalScope = rememberCoroutineScope()
32 | stateList += useSWR(key = key, fetcher = {
33 | delay(100)
34 | "fetched"
35 | }) {
36 | revalidateOnFocus = true
37 | }
38 | }
39 |
40 | composeTestRule.mainClock.advanceTimeBy(2500)
41 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.STARTED)
42 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
43 |
44 | composeTestRule.mainClock.advanceTimeBy(2500)
45 | assertEquals(listOf(null, null, "fetched", "fetched", "fetched"), stateList.map { it.data })
46 | assertEquals(listOf(null, null, null, null, null), stateList.map { it.error })
47 | assertEquals(listOf(false, true, false, false, false), stateList.map { it.isLoading })
48 | assertEquals(listOf(false, true, false, true, false), stateList.map { it.isValidating })
49 | }
50 |
51 | @Test
52 | fun noRevalidateOnFocus() {
53 | val key = Random.nextInt().toString()
54 | val stateList = mutableListOf>()
55 | composeTestRule.setContent {
56 | SWRGlobalScope = rememberCoroutineScope()
57 | stateList += useSWR(key = key, fetcher = {
58 | delay(100)
59 | "fetched"
60 | }) {
61 | revalidateOnFocus = false
62 | }
63 | }
64 |
65 | composeTestRule.mainClock.advanceTimeBy(2500)
66 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.STARTED)
67 | composeTestRule.mainClock.advanceTimeBy(5000)
68 | composeTestRule.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
69 |
70 | composeTestRule.mainClock.advanceTimeBy(2500)
71 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
72 | assertEquals(listOf(null, null, null), stateList.map { it.error })
73 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
74 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RevalidateOnMountOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.compose.runtime.rememberCoroutineScope
5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class RevalidateOnMountOptionTest {
19 |
20 | @get:Rule
21 | val composeTestRule = createAndroidComposeRule().apply {
22 | mainClock.autoAdvance = false
23 | }
24 |
25 | @Test
26 | fun withRevalidateOnMount() {
27 | val key = Random.nextInt().toString()
28 | val stateList = mutableListOf>()
29 | composeTestRule.setContent {
30 | SWRGlobalScope = rememberCoroutineScope()
31 | stateList += useSWR(key = key, fetcher = {
32 | delay(100)
33 | "fetched"
34 | }) {
35 | revalidateOnMount = true
36 | }
37 | }
38 |
39 | composeTestRule.mainClock.advanceTimeBy(2500)
40 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
41 | assertEquals(listOf(null, null, null), stateList.map { it.error })
42 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
43 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
44 | }
45 |
46 | @Test
47 | fun noRevalidateOnMount() {
48 | val key = Random.nextInt().toString()
49 | val stateList = mutableListOf>()
50 | composeTestRule.setContent {
51 | SWRGlobalScope = rememberCoroutineScope()
52 | stateList += useSWR(key = key, fetcher = {
53 | delay(100)
54 | "fetched"
55 | }) {
56 | revalidateOnMount = false
57 | }
58 | }
59 |
60 | composeTestRule.mainClock.advanceTimeBy(2500)
61 | assertEquals(listOf(null), stateList.map { it.data })
62 | assertEquals(listOf(null), stateList.map { it.error })
63 | assertEquals(listOf(false), stateList.map { it.isLoading })
64 | assertEquals(listOf(false), stateList.map { it.isValidating })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/RevalidateOnReconnectOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.NetworkRule
7 | import com.kazakago.swr.compose.internal.SWRGlobalScope
8 | import com.kazakago.swr.compose.state.SWRState
9 | import com.kazakago.swr.compose.useSWR
10 | import kotlinx.coroutines.delay
11 | import org.junit.Rule
12 | import org.junit.runner.RunWith
13 | import kotlin.random.Random
14 | import kotlin.test.Test
15 | import kotlin.test.assertEquals
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class RevalidateOnReconnectOptionTest {
19 |
20 | @get:Rule
21 | val networkRule = NetworkRule()
22 |
23 | @get:Rule
24 | val composeTestRule = createComposeRule().apply {
25 | mainClock.autoAdvance = false
26 | }
27 |
28 | @Test
29 | fun withRevalidateOnReconnect() {
30 | val key = Random.nextInt().toString()
31 | val stateList = mutableListOf>()
32 | composeTestRule.setContent {
33 | SWRGlobalScope = rememberCoroutineScope()
34 | stateList += useSWR(key = key, fetcher = {
35 | delay(100)
36 | "fetched"
37 | }) {
38 | revalidateOnReconnect = true
39 | }
40 | }
41 |
42 | networkRule.changeNetwork(isConnected = true)
43 |
44 | composeTestRule.mainClock.advanceTimeBy(2500)
45 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
46 | assertEquals(listOf(null, null, null), stateList.map { it.error })
47 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
48 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
49 |
50 | networkRule.changeNetwork(isConnected = true)
51 |
52 | composeTestRule.mainClock.advanceTimeBy(2500)
53 | assertEquals(listOf(null, null, "fetched", "fetched", "fetched"), stateList.map { it.data })
54 | assertEquals(listOf(null, null, null, null, null), stateList.map { it.error })
55 | assertEquals(listOf(false, true, false, false, false), stateList.map { it.isLoading })
56 | assertEquals(listOf(false, true, false, true, false), stateList.map { it.isValidating })
57 | }
58 |
59 | @Test
60 | fun noRevalidateOnReconnect() {
61 | val key = Random.nextInt().toString()
62 | val stateList = mutableListOf>()
63 | composeTestRule.setContent {
64 | SWRGlobalScope = rememberCoroutineScope()
65 | stateList += useSWR(key = key, fetcher = {
66 | delay(100)
67 | "fetched"
68 | }) {
69 | revalidateOnReconnect = false
70 | }
71 | }
72 |
73 | composeTestRule.mainClock.advanceTimeBy(2500)
74 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
75 | assertEquals(listOf(null, null, null), stateList.map { it.error })
76 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
77 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
78 |
79 | networkRule.changeNetwork(isConnected = true)
80 |
81 | composeTestRule.mainClock.advanceTimeBy(2500)
82 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
83 | assertEquals(listOf(null, null, null), stateList.map { it.error })
84 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
85 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/ShouldRetryOnErrorOptionTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.delay
12 | import org.junit.Rule
13 | import org.junit.runner.RunWith
14 | import kotlin.random.Random
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class ShouldRetryOnErrorOptionTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun withShouldRetryOnError() {
31 | val key = Random.nextInt().toString()
32 | val stateList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | stateList += useSWR(key = key, fetcher = {
36 | delay(100)
37 | throw DummyException1
38 | }) {
39 | shouldRetryOnError = true
40 | }
41 | }
42 |
43 | composeTestRule.mainClock.advanceTimeBy(15000)
44 | assertEquals(listOf(null, null, null, null, null), stateList.map { it.data })
45 | assertEquals(listOf(null, null, DummyException1, DummyException1, DummyException1), stateList.map { it.error })
46 | assertEquals(listOf(false, true, false, false, false), stateList.map { it.isLoading })
47 | assertEquals(listOf(false, true, false, true, false), stateList.map { it.isValidating })
48 | }
49 |
50 | @Test
51 | fun noShouldRetryOnError() {
52 | val key = Random.nextInt().toString()
53 | val stateList = mutableListOf>()
54 | composeTestRule.setContent {
55 | SWRGlobalScope = rememberCoroutineScope()
56 | stateList += useSWR(key = key, fetcher = {
57 | delay(100)
58 | throw DummyException1
59 | }) {
60 | shouldRetryOnError = false
61 | }
62 | }
63 |
64 | composeTestRule.mainClock.advanceTimeBy(15000)
65 | assertEquals(listOf(null, null, null), stateList.map { it.data })
66 | assertEquals(listOf(null, null, DummyException1), stateList.map { it.error })
67 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
68 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/swr/src/androidUnitTest/kotlin/com/kazakago/swr/compose/validation/UseSWRTest.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.validation
2 |
3 | import androidx.compose.runtime.rememberCoroutineScope
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.kazakago.swr.compose.DummyException1
7 | import com.kazakago.swr.compose.NetworkRule
8 | import com.kazakago.swr.compose.internal.SWRGlobalScope
9 | import com.kazakago.swr.compose.state.SWRState
10 | import com.kazakago.swr.compose.useSWR
11 | import kotlinx.coroutines.delay
12 | import org.junit.Rule
13 | import org.junit.runner.RunWith
14 | import kotlin.random.Random
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | @RunWith(AndroidJUnit4::class)
19 | class UseSWRTest {
20 |
21 | @get:Rule
22 | val networkRule = NetworkRule()
23 |
24 | @get:Rule
25 | val composeTestRule = createComposeRule().apply {
26 | mainClock.autoAdvance = false
27 | }
28 |
29 | @Test
30 | fun validate() {
31 | val key = Random.nextInt().toString()
32 | val stateList = mutableListOf>()
33 | composeTestRule.setContent {
34 | SWRGlobalScope = rememberCoroutineScope()
35 | stateList += useSWR(key = key, fetcher = {
36 | delay(100)
37 | "fetched"
38 | })
39 | }
40 |
41 | composeTestRule.mainClock.advanceTimeBy(2500)
42 | assertEquals(listOf(null, null, "fetched"), stateList.map { it.data })
43 | assertEquals(listOf(null, null, null), stateList.map { it.error })
44 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
45 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
46 | }
47 |
48 | @Test
49 | fun validateFailed() {
50 | val key = Random.nextInt().toString()
51 | val stateList = mutableListOf>()
52 | composeTestRule.setContent {
53 | SWRGlobalScope = rememberCoroutineScope()
54 | stateList += useSWR(key = key, fetcher = {
55 | delay(100)
56 | throw DummyException1
57 | })
58 | }
59 |
60 | composeTestRule.mainClock.advanceTimeBy(2500)
61 | assertEquals(listOf(null, null, null), stateList.map { it.data })
62 | assertEquals(listOf(null, null, DummyException1), stateList.map { it.error })
63 | assertEquals(listOf(false, true, false), stateList.map { it.isLoading })
64 | assertEquals(listOf(false, true, false), stateList.map { it.isValidating })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/UseSWR.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.kazakago.swr.compose.config.LocalSWRConfig
5 | import com.kazakago.swr.compose.config.SWRConfig
6 | import com.kazakago.swr.compose.config.SWRConfigImpl
7 | import com.kazakago.swr.compose.internal.useSWRInternal
8 | import com.kazakago.swr.compose.state.SWRState
9 | import kotlinx.coroutines.CoroutineScope
10 |
11 | @Composable
12 | public fun useSWR(
13 | key: () -> KEY?,
14 | fetcher: (suspend (key: KEY) -> DATA)? = null,
15 | scope: CoroutineScope? = null,
16 | options: SWRConfig.() -> Unit = {},
17 | ): SWRState {
18 | return useSWR(runCatching(key).getOrNull(), fetcher, scope, options)
19 | }
20 |
21 | @Composable
22 | public fun useSWR(
23 | key: KEY?,
24 | fetcher: (suspend (key: KEY) -> DATA)? = null,
25 | scope: CoroutineScope? = null,
26 | options: SWRConfig.() -> Unit = {},
27 | ): SWRState {
28 | val globalConfig = LocalSWRConfig.current
29 | val config = SWRConfigImpl.from(globalConfig).apply { options() }
30 | return useSWRInternal(key, fetcher, scope, config)
31 | }
32 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/UseSWRConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.kazakago.swr.compose.cache.LocalSWRCache
6 | import com.kazakago.swr.compose.cache.LocalSWRSystemCache
7 | import com.kazakago.swr.compose.config.LocalSWRConfig
8 | import com.kazakago.swr.compose.config.SWRConfigImpl
9 | import com.kazakago.swr.compose.mutate.SWRMutate
10 | import com.kazakago.swr.compose.mutate.SWRMutateImpl
11 | import com.kazakago.swr.compose.state.SWRConfigState
12 | import com.kazakago.swr.compose.state.SWRConfigStateImpl
13 | import com.kazakago.swr.compose.validate.SWRValidate
14 | import com.kazakago.swr.compose.validate.SWRValidateImpl
15 |
16 | @Composable
17 | public fun useSWRConfig(): SWRConfigState {
18 | val cache = LocalSWRCache.current
19 | val systemCache = LocalSWRSystemCache.current
20 | val globalConfig = LocalSWRConfig.current
21 | val config = SWRConfigImpl.from(globalConfig)
22 | val validate: SWRValidate = remember(config, cache, systemCache) {
23 | SWRValidateImpl(config, cache, systemCache, null)
24 | }
25 | val mutate: SWRMutate = remember(cache, systemCache, validate) {
26 | SWRMutateImpl(null, cache, systemCache, validate)
27 | }
28 | return SWRConfigStateImpl(
29 | mutate = mutate,
30 | config = config,
31 | cache = cache,
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/UseSWRImmutable.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.kazakago.swr.compose.config.LocalSWRConfig
5 | import com.kazakago.swr.compose.config.SWRConfig
6 | import com.kazakago.swr.compose.config.SWRConfigImpl
7 | import com.kazakago.swr.compose.internal.useSWRInternal
8 | import com.kazakago.swr.compose.state.SWRState
9 | import kotlinx.coroutines.CoroutineScope
10 |
11 | @Composable
12 | public fun useSWRImmutable(
13 | key: () -> KEY?,
14 | fetcher: (suspend (key: KEY) -> DATA)? = null,
15 | scope: CoroutineScope? = null,
16 | options: SWRConfig.() -> Unit = {},
17 | ): SWRState {
18 | return useSWRImmutable(runCatching(key).getOrNull(), fetcher, scope, options)
19 | }
20 |
21 | @Composable
22 | public fun useSWRImmutable(
23 | key: KEY?,
24 | fetcher: (suspend (key: KEY) -> DATA)? = null,
25 | scope: CoroutineScope? = null,
26 | options: SWRConfig.() -> Unit = {},
27 | ): SWRState {
28 | val globalConfig = LocalSWRConfig.current
29 | val config = SWRConfigImpl.from(globalConfig).apply {
30 | options()
31 | revalidateIfStale = false
32 | revalidateOnFocus = false
33 | revalidateOnReconnect = false
34 | }
35 | return useSWRInternal(key, fetcher, scope, config)
36 | }
37 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/UseSWRMutation.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.MutableState
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import com.kazakago.swr.compose.cache.LocalSWRCache
8 | import com.kazakago.swr.compose.cache.LocalSWRSystemCache
9 | import com.kazakago.swr.compose.config.LocalSWRConfig
10 | import com.kazakago.swr.compose.config.SWRConfigImpl
11 | import com.kazakago.swr.compose.config.SWRTriggerConfig
12 | import com.kazakago.swr.compose.state.SWRMutationState
13 | import com.kazakago.swr.compose.state.SWRMutationStateImpl
14 | import com.kazakago.swr.compose.trigger.SWRReset
15 | import com.kazakago.swr.compose.trigger.SWRResetImpl
16 | import com.kazakago.swr.compose.trigger.SWRTrigger
17 | import com.kazakago.swr.compose.trigger.SWRTriggerImpl
18 | import com.kazakago.swr.compose.validate.SWRValidate
19 | import com.kazakago.swr.compose.validate.SWRValidateImpl
20 |
21 | @Composable
22 | public fun useSWRMutation(
23 | key: () -> KEY?,
24 | fetcher: (suspend (key: KEY, arg: ARG) -> DATA),
25 | options: SWRTriggerConfig.() -> Unit = {},
26 | ): SWRMutationState {
27 | return useSWRMutation(runCatching(key).getOrNull(), fetcher, options)
28 | }
29 |
30 | @Composable
31 | public fun useSWRMutation(
32 | key: KEY?,
33 | fetcher: (suspend (key: KEY, arg: ARG) -> DATA),
34 | options: SWRTriggerConfig.() -> Unit = {},
35 | ): SWRMutationState {
36 | val cache = LocalSWRCache.current
37 | val systemCache = LocalSWRSystemCache.current
38 | val globalConfig = LocalSWRConfig.current
39 | val config = SWRConfigImpl.from(globalConfig)
40 | val mutateConfig = SWRTriggerConfig().apply { options() }
41 | val data: MutableState = remember(key) { mutableStateOf(null) }
42 | val error: MutableState = remember(key) { mutableStateOf(null) }
43 | val isMutating: MutableState = remember(key) { mutableStateOf(false) }
44 | val validate: SWRValidate = remember(mutateConfig, cache, systemCache) {
45 | SWRValidateImpl(config, cache, systemCache, null)
46 | }
47 | val trigger: SWRTrigger = remember(key, mutateConfig, cache, validate) {
48 | SWRTriggerImpl(key, mutateConfig, cache, data, error, isMutating, fetcher, validate)
49 | }
50 | val reset: SWRReset = remember(key) {
51 | SWRResetImpl(data, error, isMutating)
52 | }
53 | return remember(key, data.value, error.value, trigger, reset, isMutating.value) {
54 | SWRMutationStateImpl(data = data.value, error = error.value, trigger = trigger, reset = reset, isMutating = isMutating.value)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/UseSWRPreload.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.kazakago.swr.compose.cache.LocalSWRCache
6 | import com.kazakago.swr.compose.cache.LocalSWRSystemCache
7 | import com.kazakago.swr.compose.config.LocalSWRConfig
8 | import com.kazakago.swr.compose.config.SWRConfigImpl
9 | import com.kazakago.swr.compose.preload.SWRPreload
10 | import com.kazakago.swr.compose.preload.SWRPreloadImpl
11 | import com.kazakago.swr.compose.validate.SWRValidate
12 | import com.kazakago.swr.compose.validate.SWRValidateImpl
13 |
14 | @Composable
15 | public fun useSWRPreload(
16 | key: () -> KEY?,
17 | fetcher: (suspend (key: KEY) -> DATA)? = null,
18 | ): SWRPreload {
19 | return useSWRPreload(runCatching(key).getOrNull(), fetcher)
20 | }
21 |
22 | @Composable
23 | public fun useSWRPreload(
24 | key: KEY?,
25 | fetcher: (suspend (key: KEY) -> DATA)? = null,
26 | ): SWRPreload {
27 | val globalConfig = LocalSWRConfig.current
28 | val config = SWRConfigImpl.from(globalConfig)
29 | val cache = LocalSWRCache.current
30 | val systemCache = LocalSWRSystemCache.current
31 | if (key != null && fetcher != null) systemCache.setFetcher(key, fetcher)
32 | val validate: SWRValidate = remember(config, cache, systemCache) {
33 | SWRValidateImpl(config, cache, systemCache, fetcher)
34 | }
35 | return SWRPreloadImpl(key, validate)
36 | }
37 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/cache/SWRCache.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.cache
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.ProvidableCompositionLocal
5 | import androidx.compose.runtime.compositionLocalOf
6 | import androidx.compose.runtime.mutableStateOf
7 |
8 | public val LocalSWRCache: ProvidableCompositionLocal = compositionLocalOf {
9 | SWRCacheImpl()
10 | }
11 |
12 | public interface SWRCache {
13 | public fun state(key: KEY): MutableState
14 | public fun clear()
15 | }
16 |
17 | @Suppress("UNCHECKED_CAST")
18 | internal class SWRCacheImpl : SWRCache {
19 |
20 | private val cacheMap: MutableMap> = mutableMapOf()
21 |
22 | override fun state(key: KEY): MutableState {
23 | return (cacheMap as MutableMap>).getOrPut(key) { mutableStateOf(null) }
24 | }
25 |
26 | override fun clear() {
27 | cacheMap.clear()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/cache/SWRSystemCache.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.cache
2 |
3 | import androidx.compose.runtime.MutableState
4 | import androidx.compose.runtime.ProvidableCompositionLocal
5 | import androidx.compose.runtime.compositionLocalOf
6 | import androidx.compose.runtime.mutableStateOf
7 | import kotlinx.coroutines.Job
8 |
9 | public val LocalSWRSystemCache: ProvidableCompositionLocal = compositionLocalOf {
10 | SWRSystemCacheImpl()
11 | }
12 |
13 | public interface SWRSystemCache {
14 | public fun isValidatingState(key: KEY): MutableState
15 | public fun errorState(key: KEY): MutableState
16 | public fun getFetcher(key: KEY): (suspend (key: KEY) -> DATA)?
17 | public fun setFetcher(key: KEY, fetcher: (suspend (key: KEY) -> DATA))
18 | public fun getValidatedTimerJob(key: KEY): Job?
19 | public fun setValidatedTimerJob(key: KEY, job: Job)
20 | public fun getFocusedTimerJob(key: KEY): Job?
21 | public fun setFocusedTimerJob(key: KEY, job: Job)
22 | public fun getRetryingJobSet(key: KEY): Set
23 | public fun setRetryingJobSet(key: KEY, jobs: Set)
24 | public fun clear()
25 | }
26 |
27 | @Suppress("UNCHECKED_CAST")
28 | internal class SWRSystemCacheImpl : SWRSystemCache {
29 |
30 | private val isValidatingStateMap: MutableMap> = mutableMapOf()
31 | private val errorStateMap: MutableMap> = mutableMapOf()
32 | private val fetcherMap: MutableMap Any)> = mutableMapOf()
33 | private val validatedTimeMap: MutableMap = mutableMapOf()
34 | private val focusedTimeMap: MutableMap = mutableMapOf()
35 | private val isRetryingJobSetMap: MutableMap> = mutableMapOf()
36 |
37 | override fun isValidatingState(key: KEY): MutableState {
38 | return (isValidatingStateMap as MutableMap>).getOrPut(key) {
39 | mutableStateOf(false)
40 | }
41 | }
42 |
43 | override fun errorState(key: KEY): MutableState {
44 | return (errorStateMap as MutableMap>).getOrPut(key) {
45 | mutableStateOf(null)
46 | }
47 | }
48 |
49 | override fun getFetcher(key: KEY): (suspend (key: KEY) -> DATA)? {
50 | return (fetcherMap as MutableMap DATA)>)[key]
51 | }
52 |
53 | override fun setFetcher(key: KEY, fetcher: (suspend (key: KEY) -> DATA)) {
54 | (fetcherMap as MutableMap DATA)>)[key] = fetcher
55 | }
56 |
57 | override fun getValidatedTimerJob(key: KEY): Job? {
58 | return (validatedTimeMap as MutableMap)[key]
59 | }
60 |
61 | override fun setValidatedTimerJob(key: KEY, job: Job) {
62 | (validatedTimeMap as MutableMap)[key] = job
63 | }
64 |
65 | override fun getFocusedTimerJob(key: KEY): Job? {
66 | return (focusedTimeMap as MutableMap)[key]
67 | }
68 |
69 | override fun setFocusedTimerJob(key: KEY, job: Job) {
70 | (focusedTimeMap as MutableMap)[key] = job
71 | }
72 |
73 | override fun getRetryingJobSet(key: KEY): Set {
74 | return (isRetryingJobSetMap as MutableMap>)[key] ?: emptySet()
75 | }
76 |
77 | override fun setRetryingJobSet(key: KEY, jobs: Set) {
78 | (isRetryingJobSetMap as MutableMap>)[key] = jobs
79 | }
80 |
81 | override fun clear() {
82 | isValidatingStateMap.clear()
83 | errorStateMap.clear()
84 | fetcherMap.clear()
85 | validatedTimeMap.clear()
86 | focusedTimeMap.clear()
87 | isRetryingJobSetMap.clear()
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/config/SWRInfiniteConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.config
2 |
3 | import com.kazakago.swr.compose.validate.SWRValidate
4 | import com.kazakago.swr.compose.validate.SWRValidateOptions
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlin.time.Duration
7 |
8 | @Suppress("UNCHECKED_CAST")
9 | public data class SWRInfiniteConfig(
10 | override var fetcher: (suspend (key: KEY) -> DATA)?,
11 | override var revalidateIfStale: Boolean,
12 | override var revalidateOnMount: Boolean?,
13 | override var revalidateOnFocus: Boolean,
14 | override var revalidateOnReconnect: Boolean,
15 | override var refreshInterval: Duration,
16 | override var refreshWhenHidden: Boolean,
17 | override var refreshWhenOffline: Boolean,
18 | override var shouldRetryOnError: Boolean,
19 | override var dedupingInterval: Duration,
20 | override var focusThrottleInterval: Duration,
21 | override var loadingTimeout: Duration,
22 | override var errorRetryInterval: Duration,
23 | override var errorRetryCount: Int?,
24 | override var fallback: Map,
25 | override var fallbackData: DATA? = null,
26 | override var keepPreviousData: Boolean,
27 | override var onLoadingSlow: ((key: KEY, config: SWRConfig) -> Unit)?,
28 | override var onSuccess: ((data: DATA, key: KEY, config: SWRConfig) -> Unit)?,
29 | override var onError: ((error: Throwable, key: KEY, config: SWRConfig) -> Unit)?,
30 | override var onErrorRetry: suspend (error: Throwable, key: KEY, config: SWRConfig, revalidate: SWRValidate, options: SWRValidateOptions) -> Unit,
31 | override var isPaused: () -> Boolean,
32 | public var initialSize: Int = 1,
33 | public var revalidateAll: Boolean = false,
34 | public var revalidateFirstPage: Boolean = true,
35 | public var persistSize: Boolean = false,
36 | override var scope: CoroutineScope?,
37 | ) : SWRConfig {
38 |
39 | internal companion object {
40 | internal fun from(globalConfig: SWRGlobalConfig): SWRInfiniteConfig {
41 | val config = globalConfig.copy()
42 | return SWRInfiniteConfig(
43 | fetcher = config.fetcher as (suspend (key: KEY) -> DATA)?,
44 | revalidateIfStale = config.revalidateIfStale,
45 | revalidateOnMount = config.revalidateOnMount,
46 | revalidateOnFocus = config.revalidateOnFocus,
47 | revalidateOnReconnect = config.revalidateOnReconnect,
48 | refreshInterval = config.refreshInterval,
49 | refreshWhenHidden = config.refreshWhenHidden,
50 | refreshWhenOffline = config.refreshWhenOffline,
51 | shouldRetryOnError = config.shouldRetryOnError,
52 | dedupingInterval = config.dedupingInterval,
53 | focusThrottleInterval = config.focusThrottleInterval,
54 | loadingTimeout = config.loadingTimeout,
55 | errorRetryInterval = config.errorRetryInterval,
56 | errorRetryCount = config.errorRetryCount,
57 | fallback = config.fallback as Map,
58 | keepPreviousData = config.keepPreviousData,
59 | onLoadingSlow = config.onLoadingSlow as ((key: KEY, config: SWRConfig) -> Unit)?,
60 | onSuccess = config.onSuccess as ((data: DATA, key: KEY, config: SWRConfig) -> Unit)?,
61 | onError = config.onError as ((error: Throwable, key: KEY, config: SWRConfig) -> Unit)?,
62 | onErrorRetry = config.onErrorRetry as suspend (error: Throwable, key: KEY, config: SWRConfig, revalidate: SWRValidate, options: SWRValidateOptions) -> Unit,
63 | isPaused = config.isPaused,
64 | scope = config.scope,
65 | )
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/config/SWRMutateConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.config
2 |
3 | public data class SWRMutateConfig(
4 | var optimisticData: DATA? = null,
5 | var revalidate: Boolean = true,
6 | var populateCache: Boolean = true,
7 | var rollbackOnError: Boolean = true,
8 | var throwOnError: Boolean = true,
9 | )
10 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/config/SWRTriggerConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.config
2 |
3 | public data class SWRTriggerConfig(
4 | var optimisticData: DATA? = null,
5 | var revalidate: Boolean = true,
6 | var populateCache: Boolean = false,
7 | var rollbackOnError: Boolean = true,
8 | var throwOnError: Boolean = true,
9 | var onSuccess: ((data: DATA, key: KEY, config: SWRTriggerConfig) -> Unit)? = null,
10 | var onError: ((error: Throwable, key: KEY, config: SWRTriggerConfig) -> Unit)? = null,
11 | )
12 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/internal/DataHolder.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.internal
2 |
3 | internal data class DataHolder(
4 | var data: DATA? = null,
5 | )
6 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/internal/GlobalKonnection.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.internal
2 |
3 | import androidx.annotation.VisibleForTesting
4 | import dev.tmapps.konnection.Konnection
5 |
6 | @VisibleForTesting
7 | internal var GlobalKonnection: Konnection
8 | set(value) {
9 | internalKonnection = value
10 | }
11 | get() {
12 | var localKonnection = internalKonnection
13 | if (localKonnection == null) {
14 | localKonnection = Konnection.instance
15 | internalKonnection = localKonnection
16 | }
17 | return localKonnection
18 | }
19 | private var internalKonnection: Konnection? = null
20 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/internal/SWRGlobalScope.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.internal
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlin.coroutines.EmptyCoroutineContext
5 |
6 | internal var SWRGlobalScope: CoroutineScope = CoroutineScope(EmptyCoroutineContext)
7 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/internal/UseSWRInternal.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.MutableState
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.rememberCoroutineScope
8 | import com.kazakago.swr.compose.cache.LocalSWRCache
9 | import com.kazakago.swr.compose.cache.LocalSWRSystemCache
10 | import com.kazakago.swr.compose.config.SWRConfig
11 | import com.kazakago.swr.compose.mutate.SWRMutate
12 | import com.kazakago.swr.compose.mutate.SWRMutateImpl
13 | import com.kazakago.swr.compose.state.SWRState
14 | import com.kazakago.swr.compose.state.SWRStateImpl
15 | import com.kazakago.swr.compose.validate.SWRValidate
16 | import com.kazakago.swr.compose.validate.SWRValidateImpl
17 | import kotlinx.coroutines.CoroutineScope
18 | import kotlinx.coroutines.launch
19 |
20 | @Composable
21 | internal fun useSWRInternal(
22 | key: KEY?,
23 | fetcher: (suspend (key: KEY) -> DATA)?,
24 | scope: CoroutineScope?,
25 | config: SWRConfig,
26 | ): SWRState {
27 | val cache = LocalSWRCache.current
28 | val systemCache = LocalSWRSystemCache.current
29 | val currentScope = scope ?: config.scope ?: rememberCoroutineScope()
30 | if (key != null && fetcher != null) systemCache.setFetcher(key, fetcher)
31 | val validate: SWRValidate = remember(config, cache, systemCache) {
32 | SWRValidateImpl(config, cache, systemCache, fetcher)
33 | }
34 | val mutate: SWRMutate = remember(key, cache, systemCache, validate) {
35 | SWRMutateImpl(key, cache, systemCache, validate)
36 | }
37 |
38 | val loadedDataHolder: DataHolder = remember { DataHolder() }
39 | val loadedData: DATA? by remember(key) { cache.state(key) }
40 | val fallbackData: DATA? = remember(key) { config.fallbackData ?: config.fallback[key] ?: (if (config.keepPreviousData) loadedDataHolder.data else null) }
41 | val error: MutableState = remember(key) { systemCache.errorState(key) }
42 | val isValidating: MutableState = remember(key) { systemCache.isValidatingState(key) }
43 | loadedDataHolder.data = loadedData
44 |
45 | RevalidateOnMount(key, config, isValidating) { currentScope.launch { validate(key) } }
46 | RevalidateOnFocus(key, config) { currentScope.launch { validate(key) } }
47 | RevalidateOnReconnect(key, config) { currentScope.launch { validate(key) } }
48 | RefreshInterval(key, config) { currentScope.launch { validate(key) } }
49 |
50 | return remember(key, loadedData, fallbackData, error.value, isValidating.value, mutate) {
51 | val data = loadedData ?: fallbackData
52 | val isLoading: Boolean = (loadedData == null) && (error.value == null) && isValidating.value
53 | SWRStateImpl(data = data, error = error.value, isLoading = isLoading, isValidating = isValidating.value, mutate = mutate)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/mutate/SWRMutate.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.mutate
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kazakago.swr.compose.cache.SWRCache
5 | import com.kazakago.swr.compose.cache.SWRSystemCache
6 | import com.kazakago.swr.compose.config.SWRMutateConfig
7 | import com.kazakago.swr.compose.validate.SWRValidate
8 |
9 | public interface SWRMutate {
10 | public suspend operator fun invoke(
11 | key: KEY? = null,
12 | data: (suspend () -> DATA)? = null,
13 | options: SWRMutateConfig.() -> Unit = {},
14 | )
15 |
16 | public companion object {
17 | public fun empty(): SWRMutate = object : SWRMutate {
18 | override suspend fun invoke(key: KEY?, data: (suspend () -> DATA)?, options: SWRMutateConfig.() -> Unit) {}
19 | }
20 | }
21 | }
22 |
23 | @Immutable
24 | internal data class SWRMutateImpl(
25 | private val key: KEY?,
26 | private val cache: SWRCache,
27 | private val systemCache: SWRSystemCache,
28 | private val validate: SWRValidate,
29 | ) : SWRMutate {
30 |
31 | override suspend operator fun invoke(
32 | key: KEY?,
33 | data: (suspend () -> DATA)?,
34 | options: SWRMutateConfig.() -> Unit,
35 | ) {
36 | val currentKey = key ?: this.key ?: return
37 | val config = SWRMutateConfig().apply { options() }
38 | val oldData: DATA? = cache.state(currentKey).value
39 | if (config.optimisticData != null) {
40 | cache.state(currentKey).value = config.optimisticData
41 | systemCache.errorState(currentKey).value = null
42 | }
43 | runCatching {
44 | if (data != null) data() else null
45 | }.onSuccess { newData ->
46 | if (config.populateCache && newData != null) {
47 | cache.state(currentKey).value = newData
48 | systemCache.errorState(currentKey).value = null
49 | }
50 | if (config.revalidate) {
51 | validate(currentKey)
52 | }
53 | }.onFailure { throwable ->
54 | if (config.rollbackOnError) {
55 | cache.state(currentKey).value = oldData
56 | }
57 | if (config.throwOnError) {
58 | throw throwable
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/preload/SWRPreload.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.preload
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kazakago.swr.compose.validate.SWRValidate
5 |
6 | public interface SWRPreload {
7 | public suspend operator fun invoke()
8 | }
9 |
10 | @Immutable
11 | internal data class SWRPreloadImpl(
12 | private val key: KEY?,
13 | private val validate: SWRValidate,
14 | ) : SWRPreload {
15 |
16 | override suspend operator fun invoke() {
17 | validate(key)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/retry/SWRRetryDefault.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.retry
2 |
3 | import com.kazakago.swr.compose.config.SWRConfig
4 | import com.kazakago.swr.compose.validate.SWRValidate
5 | import com.kazakago.swr.compose.validate.SWRValidateOptions
6 | import kotlinx.coroutines.delay
7 | import kotlin.math.floor
8 | import kotlin.random.Random
9 |
10 | public val OnErrorRetryDefault: suspend (error: Throwable, key: Any, config: SWRConfig<*, *>, revalidate: SWRValidate, options: SWRValidateOptions) -> Unit = { _, key, config, revalidate, options ->
11 | if (!options.dedupe && config.errorRetryCount.let { it == null || options.retryCount <= it }) {
12 | val exponentialBackoff = floor((Random.nextDouble() + 0.5) * 1.shl(options.retryCount)).toLong() * config.errorRetryInterval.inWholeMilliseconds
13 | delay(exponentialBackoff)
14 | revalidate(key, options)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/swr/src/commonMain/kotlin/com/kazakago/swr/compose/state/SWRConfigState.kt:
--------------------------------------------------------------------------------
1 | package com.kazakago.swr.compose.state
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.runtime.mutableStateOf
5 | import com.kazakago.swr.compose.cache.SWRCache
6 | import com.kazakago.swr.compose.config.SWRConfig
7 | import com.kazakago.swr.compose.config.SWRConfigImpl
8 | import com.kazakago.swr.compose.config.SWRGlobalConfig
9 | import com.kazakago.swr.compose.mutate.SWRMutate
10 |
11 | public interface SWRConfigState {
12 | public val mutate: SWRMutate
13 | public val config: SWRConfig
14 | public val cache: SWRCache
15 |
16 | public operator fun component1(): SWRMutate = mutate
17 | public operator fun component2(): SWRConfig = config
18 | public operator fun component3(): SWRCache = cache
19 |
20 | public companion object {
21 | public fun empty(
22 | mutate: SWRMutate