From e7860e4f558ee12403a86c5fde4667cf9bb8b5e5 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 15:27:10 +0530 Subject: [PATCH 01/10] api: add support for pagination Signed-off-by: Harsh Shandilya --- app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt | 2 +- .../src/main/java/dev/msfjarvis/lobsters/api/LobstersApi.kt | 3 ++- .../test/java/dev/msfjarvis/lobsters/api/LobstersApiTest.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt index 9780be37..78347472 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt @@ -37,7 +37,7 @@ class MainActivity : AppCompatActivity() { val coroutineScope = rememberCoroutineScope() val posts = mutableStateListOf() coroutineScope.launch { - posts.addAll(apiClient.getHottestPosts()) + posts.addAll(apiClient.getHottestPosts(1)) } LobstersApp(posts) } diff --git a/lobsters-api/src/main/java/dev/msfjarvis/lobsters/api/LobstersApi.kt b/lobsters-api/src/main/java/dev/msfjarvis/lobsters/api/LobstersApi.kt index f90b4e1b..4a6d1621 100644 --- a/lobsters-api/src/main/java/dev/msfjarvis/lobsters/api/LobstersApi.kt +++ b/lobsters-api/src/main/java/dev/msfjarvis/lobsters/api/LobstersApi.kt @@ -2,8 +2,9 @@ package dev.msfjarvis.lobsters.api import dev.msfjarvis.lobsters.model.LobstersPost import retrofit2.http.GET +import retrofit2.http.Query interface LobstersApi { @GET("hottest.json") - suspend fun getHottestPosts(): List + suspend fun getHottestPosts(@Query("page") page: Int): List } diff --git a/lobsters-api/src/test/java/dev/msfjarvis/lobsters/api/LobstersApiTest.kt b/lobsters-api/src/test/java/dev/msfjarvis/lobsters/api/LobstersApiTest.kt index 9e567ce9..d28903b6 100644 --- a/lobsters-api/src/test/java/dev/msfjarvis/lobsters/api/LobstersApiTest.kt +++ b/lobsters-api/src/test/java/dev/msfjarvis/lobsters/api/LobstersApiTest.kt @@ -27,7 +27,7 @@ class LobstersApiTest { @Test fun `api gets correct number of items`() = runBlocking { - val posts = apiClient.getHottestPosts() + val posts = apiClient.getHottestPosts(1) assertEquals(25, posts.size) } From 699a475315f6d5a69e0ed79b87a64d3d86a23086 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 15:32:31 +0530 Subject: [PATCH 02/10] app: use lifecycleScope for fetching posts Signed-off-by: Harsh Shandilya --- app/build.gradle | 1 + .../main/java/dev/msfjarvis/lobsters/MainActivity.kt | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 048198ae..5c8cb807 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,6 +67,7 @@ dependencies { implementation "androidx.compose.ui:ui-text:$compose_version" implementation "androidx.compose.ui:ui-text-android:$compose_version" implementation "androidx.compose.ui:ui-unit:$compose_version" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha07" implementation "androidx.ui:ui-tooling:$compose_version" implementation 'com.google.android.material:material:1.3.0-alpha02' implementation "com.google.dagger:hilt-android:$hilt_version" diff --git a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt index 78347472..f2a257de 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt @@ -10,16 +10,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Providers import androidx.compose.runtime.ambientOf import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import dev.msfjarvis.lobsters.api.LobstersApi import dev.msfjarvis.lobsters.model.LobstersPost import dev.msfjarvis.lobsters.ui.LobstersItem import dev.msfjarvis.lobsters.ui.LobstersTheme import dev.msfjarvis.lobsters.urllauncher.UrlLauncher -import kotlinx.coroutines.launch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import javax.inject.Inject val UrlLauncherAmbient = ambientOf { error("Needs to be provided") } @@ -34,10 +35,11 @@ class MainActivity : AppCompatActivity() { setContent { Providers(UrlLauncherAmbient provides urlLauncher) { LobstersTheme { - val coroutineScope = rememberCoroutineScope() val posts = mutableStateListOf() - coroutineScope.launch { - posts.addAll(apiClient.getHottestPosts(1)) + lifecycleScope.launchWhenCreated { + withContext(Dispatchers.IO) { + posts.addAll(apiClient.getHottestPosts(1)) + } } LobstersApp(posts) } From 96c25c428ddf523645018e72988b9dc05d66b1e5 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 16:27:41 +0530 Subject: [PATCH 03/10] data: add database for lobsters posts Signed-off-by: Harsh Shandilya --- build.gradle | 1 + data/build.gradle | 5 +- .../data/source/LobstersApiTypeConverters.kt | 63 +++++++++++++++++++ .../lobsters/data/source/PostsDao.kt | 23 +++++++ .../lobsters/data/source/PostsDatabase.kt | 21 +++++++ model/build.gradle | 1 - 6 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 data/src/main/java/dev/msfjarvis/lobsters/data/source/LobstersApiTypeConverters.kt create mode 100644 data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDao.kt create mode 100644 data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDatabase.kt diff --git a/build.gradle b/build.gradle index 8f992308..0f5d9438 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { dagger_version = '2.29.1' hilt_version = '2.29-alpha' kotlin_version = '1.4.10' + moshi_version = '1.9.3' room_version = '2.3.0-alpha02' } repositories { diff --git a/data/build.gradle b/data/build.gradle index 97b4eaca..ab8d749c 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -4,7 +4,10 @@ plugins { } dependencies { + implementation project(":model") kapt "androidx.room:room-compiler:$room_version" api "androidx.room:room-runtime:$room_version" - api "androidx.room:room-ktx:$room_version" + implementation "androidx.room:room-ktx:$room_version" + implementation "com.squareup.moshi:moshi:$moshi_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" } diff --git a/data/src/main/java/dev/msfjarvis/lobsters/data/source/LobstersApiTypeConverters.kt b/data/src/main/java/dev/msfjarvis/lobsters/data/source/LobstersApiTypeConverters.kt new file mode 100644 index 00000000..11dd1f9a --- /dev/null +++ b/data/src/main/java/dev/msfjarvis/lobsters/data/source/LobstersApiTypeConverters.kt @@ -0,0 +1,63 @@ +package dev.msfjarvis.lobsters.data.source + +import androidx.room.TypeConverter +import com.squareup.moshi.Moshi +import dev.msfjarvis.lobsters.model.KeybaseSignature +import dev.msfjarvis.lobsters.model.KeybaseSignatureJsonAdapter +import dev.msfjarvis.lobsters.model.LobstersPost +import dev.msfjarvis.lobsters.model.LobstersPostJsonAdapter +import dev.msfjarvis.lobsters.model.Submitter +import dev.msfjarvis.lobsters.model.SubmitterJsonAdapter + +object LobstersApiTypeConverters { + private val moshi = Moshi.Builder().build() + private const val SEPARATOR = "," + + @TypeConverter + @JvmStatic + fun toSubmitterUser(value: String?): Submitter? { + return value?.let { SubmitterJsonAdapter(moshi).fromJson(value) } + } + + @TypeConverter + @JvmStatic + fun fromSubmitterUser(value: Submitter?): String? { + return value?.let { SubmitterJsonAdapter(moshi).toJson(value) } + } + + @TypeConverter + @JvmStatic + fun toKeybaseSignature(value: String?): KeybaseSignature? { + return value?.let { KeybaseSignatureJsonAdapter(moshi).fromJson(value) } + } + + @TypeConverter + @JvmStatic + fun fromKeybaseSignature(value: KeybaseSignature?): String? { + return value?.let { KeybaseSignatureJsonAdapter(moshi).toJson(value) } + } + + @TypeConverter + @JvmStatic + fun toLobstersPost(value: String?): LobstersPost? { + return value?.let { LobstersPostJsonAdapter(moshi).fromJson(value) } + } + + @TypeConverter + @JvmStatic + fun fromLobstersPost(value: LobstersPost?): String? { + return value?.let { LobstersPostJsonAdapter(moshi).toJson(value) } + } + + @TypeConverter + @JvmStatic + fun toTagList(value: String?): List? { + return value?.split(SEPARATOR) + } + + @TypeConverter + @JvmStatic + fun fromTagList(value: List?): String? { + return value?.joinToString(SEPARATOR) + } +} diff --git a/data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDao.kt b/data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDao.kt new file mode 100644 index 00000000..9247ebe2 --- /dev/null +++ b/data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDao.kt @@ -0,0 +1,23 @@ +package dev.msfjarvis.lobsters.data.source + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import dev.msfjarvis.lobsters.model.LobstersPost +import kotlinx.coroutines.flow.Flow + +@Dao +abstract class PostsDao { + @Query("SELECT * FROM lobsters_posts") + abstract fun loadPosts(): Flow> + + @Insert + abstract suspend fun insertPosts(vararg posts: LobstersPost) + + @Delete + abstract suspend fun deletePosts(vararg posts: LobstersPost) + + @Query("DELETE FROM lobsters_posts") + abstract suspend fun deleteAllPosts() +} diff --git a/data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDatabase.kt b/data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDatabase.kt new file mode 100644 index 00000000..0640e663 --- /dev/null +++ b/data/src/main/java/dev/msfjarvis/lobsters/data/source/PostsDatabase.kt @@ -0,0 +1,21 @@ +package dev.msfjarvis.lobsters.data.source + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import dev.msfjarvis.lobsters.model.LobstersPost + +@Database( + entities = [ + LobstersPost::class, + ], + version = 1, + exportSchema = false, +) +@TypeConverters( + LobstersApiTypeConverters::class, + DateTimeTypeConverters::class, +) +abstract class PostsDatabase : RoomDatabase() { + abstract fun postsDao(): PostsDao +} diff --git a/model/build.gradle b/model/build.gradle index 51aade2d..182b824c 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -4,7 +4,6 @@ plugins { } dependencies { - def moshi_version = "1.9.3" api "androidx.room:room-runtime:$room_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" implementation "com.squareup.moshi:moshi:$moshi_version" From 29e72da3277d321c44f3baa66333362454c3dd9c Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 16:28:00 +0530 Subject: [PATCH 04/10] app: add persistence module Signed-off-by: Harsh Shandilya --- .../lobsters/di/PersistenceModule.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/src/main/java/dev/msfjarvis/lobsters/di/PersistenceModule.kt diff --git a/app/src/main/java/dev/msfjarvis/lobsters/di/PersistenceModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/di/PersistenceModule.kt new file mode 100644 index 00000000..d75303de --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/di/PersistenceModule.kt @@ -0,0 +1,22 @@ +package dev.msfjarvis.lobsters.di + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ApplicationContext +import dev.msfjarvis.lobsters.data.source.PostsDatabase + +@Module +@InstallIn(ActivityComponent::class) +object PersistenceModule { + + @Provides + fun providePostsDatabase(@ApplicationContext context: Context): PostsDatabase { + return Room.databaseBuilder(context, PostsDatabase::class.java, "posts.db") + .fallbackToDestructiveMigration() + .build() + } +} From 64ac515ed013aef7d76a17e3e41f1ebf3e77f34b Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 16:35:08 +0530 Subject: [PATCH 05/10] build: force correct coroutines and kotlin-reflect artifacts Signed-off-by: Harsh Shandilya --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 0f5d9438..a8f5c879 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ buildscript { ext { compose_version = '1.0.0-alpha03' + coroutines_version = '1.3.9' dagger_version = '2.29.1' hilt_version = '2.29-alpha' kotlin_version = '1.4.10' @@ -50,6 +51,13 @@ subprojects { targetCompatibility JavaVersion.VERSION_1_8 } } + configurations.all { + resolutionStrategy { + force "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + force "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + force "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + } + } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { jvmTarget = '1.8' From b3ee3f44c424496da841d7f243da6f8a6083bee7 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 16:38:55 +0530 Subject: [PATCH 06/10] build: commonize application of kotlin-android plugin Signed-off-by: Harsh Shandilya --- app/build.gradle | 1 - build.gradle | 1 + data/build.gradle | 1 - lobsters-api/build.gradle | 1 - model/build.gradle | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5c8cb807..5d3ac59a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'kotlin-android' id 'kotlin-kapt' id 'dagger.hilt.android.plugin' } diff --git a/build.gradle b/build.gradle index a8f5c879..16c3e666 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ subprojects { } else { apply plugin: 'com.android.library' } + apply plugin: 'kotlin-android' android { compileSdkVersion 30 diff --git a/data/build.gradle b/data/build.gradle index ab8d749c..b44e1a5a 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'kotlin-android' id 'kotlin-kapt' } diff --git a/lobsters-api/build.gradle b/lobsters-api/build.gradle index 4de188c3..8f3d6216 100644 --- a/lobsters-api/build.gradle +++ b/lobsters-api/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'kotlin-android' id 'kotlin-kapt' } diff --git a/model/build.gradle b/model/build.gradle index 182b824c..5ed00f0f 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'kotlin-android' id 'kotlin-kapt' } From 5260f454ef082133386ab99dc9c571db34cf48ea Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 21:35:53 +0530 Subject: [PATCH 07/10] build: add lifecycle-viewmodel-ktx Signed-off-by: Harsh Shandilya --- app/build.gradle | 3 ++- build.gradle | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5d3ac59a..f1defb04 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -66,7 +66,8 @@ dependencies { implementation "androidx.compose.ui:ui-text:$compose_version" implementation "androidx.compose.ui:ui-text-android:$compose_version" implementation "androidx.compose.ui:ui-unit:$compose_version" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha07" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.ui:ui-tooling:$compose_version" implementation 'com.google.android.material:material:1.3.0-alpha02' implementation "com.google.dagger:hilt-android:$hilt_version" diff --git a/build.gradle b/build.gradle index 16c3e666..72617ef8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { dagger_version = '2.29.1' hilt_version = '2.29-alpha' kotlin_version = '1.4.10' + lifecycle_version = '2.3.0-alpha07' moshi_version = '1.9.3' room_version = '2.3.0-alpha02' } From e1c9b34bbe68a5ea5cf82a0eba0f7cfaaaa42aa2 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 22:06:56 +0530 Subject: [PATCH 08/10] app: setup pagination and a viewmodel for posts Signed-off-by: Harsh Shandilya --- app/build.gradle | 10 ++++-- .../dev/msfjarvis/lobsters/MainActivity.kt | 31 +++++++++--------- .../lobsters/data/LobstersViewModel.kt | 32 +++++++++++++++++++ build.gradle | 5 +-- 4 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index f1defb04..f9e3ea34 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,11 +50,13 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { dependencies { - kapt "com.google.dagger:hilt-android-compiler:$hilt_version" + kapt "com.google.dagger:hilt-android-compiler:$hilt_dagger_version" + kapt "androidx.hilt:hilt-compiler:$hilt_androidx_version" implementation(project(":data")) implementation(project(":lobsters-api")) implementation(project(":model")) implementation 'androidx.core:core-ktx:1.5.0-alpha03' + implementation 'androidx.activity:activity-ktx:1.2.0-alpha08' implementation 'androidx.appcompat:appcompat:1.3.0-alpha02' implementation "androidx.compose.foundation:foundation:$compose_version" implementation "androidx.compose.foundation:foundation-layout:$compose_version" @@ -66,12 +68,14 @@ dependencies { implementation "androidx.compose.ui:ui-text:$compose_version" implementation "androidx.compose.ui:ui-text-android:$compose_version" implementation "androidx.compose.ui:ui-unit:$compose_version" + implementation "androidx.hilt:hilt-lifecycle-viewmodel:$hilt_androidx_version" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.ui:ui-tooling:$compose_version" implementation 'com.google.android.material:material:1.3.0-alpha02' - implementation "com.google.dagger:hilt-android:$hilt_version" - androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" + implementation "com.google.dagger:hilt-android:$hilt_dagger_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_dagger_version" testImplementation 'junit:junit:4.13' androidTestImplementation "androidx.ui:ui-test:$compose_version" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.10" diff --git a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt index f2a257de..f1348c0d 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt @@ -1,47 +1,42 @@ package dev.msfjarvis.lobsters import android.os.Bundle +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.Text -import androidx.compose.foundation.lazy.LazyColumnFor +import androidx.compose.foundation.lazy.LazyColumnForIndexed import androidx.compose.material.Scaffold import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.Providers import androidx.compose.runtime.ambientOf -import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.setContent import androidx.compose.ui.res.stringResource -import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import dev.msfjarvis.lobsters.api.LobstersApi -import dev.msfjarvis.lobsters.model.LobstersPost +import dev.msfjarvis.lobsters.data.LobstersViewModel import dev.msfjarvis.lobsters.ui.LobstersItem import dev.msfjarvis.lobsters.ui.LobstersTheme import dev.msfjarvis.lobsters.urllauncher.UrlLauncher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Inject val UrlLauncherAmbient = ambientOf { error("Needs to be provided") } @AndroidEntryPoint +@ExperimentalCoroutinesApi class MainActivity : AppCompatActivity() { @Inject lateinit var urlLauncher: UrlLauncher @Inject lateinit var apiClient: LobstersApi + private val viewModel: LobstersViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Providers(UrlLauncherAmbient provides urlLauncher) { LobstersTheme { - val posts = mutableStateListOf() - lifecycleScope.launchWhenCreated { - withContext(Dispatchers.IO) { - posts.addAll(apiClient.getHottestPosts(1)) - } - } - LobstersApp(posts) + LobstersApp(viewModel) } } } @@ -49,15 +44,21 @@ class MainActivity : AppCompatActivity() { } @Composable +@ExperimentalCoroutinesApi fun LobstersApp( - items: List, + viewModel: LobstersViewModel ) { val urlLauncher = UrlLauncherAmbient.current + val state = viewModel.posts.collectAsState() + val lastIndex = state.value.lastIndex Scaffold( topBar = { TopAppBar({ Text(text = stringResource(R.string.app_name)) }) }, bodyContent = { - LazyColumnFor(items) { item -> + LazyColumnForIndexed(state.value) { index, item -> + if (lastIndex == index) { + viewModel.getMorePosts() + } LobstersItem(item) { post -> urlLauncher.launch(post.url) } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt new file mode 100644 index 00000000..12e0039d --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt @@ -0,0 +1,32 @@ +package dev.msfjarvis.lobsters.data + +import androidx.hilt.lifecycle.ViewModelInject +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dev.msfjarvis.lobsters.api.LobstersApi +import dev.msfjarvis.lobsters.model.LobstersPost +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + + +@ExperimentalCoroutinesApi +class LobstersViewModel @ViewModelInject constructor( + private val lobstersApi: LobstersApi, +) : ViewModel() { + private var apiPage = 1 + private val _posts = MutableStateFlow>(emptyList()) + val posts: StateFlow> get() = _posts + + init { + getMorePosts() + } + + fun getMorePosts() { + viewModelScope.launch { + _posts.value += lobstersApi.getHottestPosts(apiPage) + apiPage += 1 + } + } +} diff --git a/build.gradle b/build.gradle index 72617ef8..a6d55a8d 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,8 @@ buildscript { compose_version = '1.0.0-alpha03' coroutines_version = '1.3.9' dagger_version = '2.29.1' - hilt_version = '2.29-alpha' + hilt_androidx_version = '1.0.0-alpha02' + hilt_dagger_version = '2.29-alpha' kotlin_version = '1.4.10' lifecycle_version = '2.3.0-alpha07' moshi_version = '1.9.3' @@ -17,7 +18,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:4.2.0-alpha12" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_dagger_version" } } From 22abaa15c26030d221896733295e50225e7a71f3 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 22:09:15 +0530 Subject: [PATCH 09/10] model: add default value for submitter karma The API sometimes doesn't pass this value Signed-off-by: Harsh Shandilya --- model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt b/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt index 929bc520..1260d02a 100644 --- a/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt +++ b/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt @@ -13,7 +13,7 @@ class Submitter( val about: String, @Json(name = "is_moderator") val isModerator: Boolean, - val karma: Long, + val karma: Long = 0, @Json(name = "avatar_url") val avatarUrl: String, @Json(name = "invited_by_user") From 590bd2dfd8924a0396a073767d4ea41b5f515582 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 23 Sep 2020 22:14:20 +0530 Subject: [PATCH 10/10] app: enable kotlinx.coroutines.ExperimentalCoroutinesApi for entire module Signed-off-by: Harsh Shandilya --- app/build.gradle | 7 ++++++- app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt | 3 --- .../java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt | 2 -- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f9e3ea34..f7699199 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,7 +44,12 @@ android { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { - freeCompilerArgs += ["-Xallow-jvm-ir-dependencies", "-Xskip-prerelease-check", "-Xopt-in=kotlin.RequiresOptIn"] + freeCompilerArgs += [ + "-Xallow-jvm-ir-dependencies", + "-Xskip-prerelease-check", + "-Xopt-in=kotlin.RequiresOptIn", + "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + ] } } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt index f1348c0d..4a37df84 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/MainActivity.kt @@ -19,13 +19,11 @@ import dev.msfjarvis.lobsters.data.LobstersViewModel import dev.msfjarvis.lobsters.ui.LobstersItem import dev.msfjarvis.lobsters.ui.LobstersTheme import dev.msfjarvis.lobsters.urllauncher.UrlLauncher -import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Inject val UrlLauncherAmbient = ambientOf { error("Needs to be provided") } @AndroidEntryPoint -@ExperimentalCoroutinesApi class MainActivity : AppCompatActivity() { @Inject lateinit var urlLauncher: UrlLauncher @Inject lateinit var apiClient: LobstersApi @@ -44,7 +42,6 @@ class MainActivity : AppCompatActivity() { } @Composable -@ExperimentalCoroutinesApi fun LobstersApp( viewModel: LobstersViewModel ) { diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt index 12e0039d..cd32193f 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/LobstersViewModel.kt @@ -5,13 +5,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dev.msfjarvis.lobsters.api.LobstersApi import dev.msfjarvis.lobsters.model.LobstersPost -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -@ExperimentalCoroutinesApi class LobstersViewModel @ViewModelInject constructor( private val lobstersApi: LobstersApi, ) : ViewModel() {