Merge pull request #22 from msfjarvis/pagination-prep

This commit is contained in:
Harsh Shandilya 2020-09-23 22:17:13 +05:30 committed by GitHub
commit 7b42915a1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 209 additions and 28 deletions

View file

@ -1,5 +1,4 @@
plugins {
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
@ -45,17 +44,24 @@ 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",
]
}
}
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"
@ -67,10 +73,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"

View file

@ -1,25 +1,24 @@
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.rememberCoroutineScope
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.stringResource
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.launch
import javax.inject.Inject
val UrlLauncherAmbient = ambientOf<UrlLauncher> { error("Needs to be provided") }
@ -28,18 +27,14 @@ val UrlLauncherAmbient = ambientOf<UrlLauncher> { error("Needs to be provided")
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 coroutineScope = rememberCoroutineScope()
val posts = mutableStateListOf<LobstersPost>()
coroutineScope.launch {
posts.addAll(apiClient.getHottestPosts())
}
LobstersApp(posts)
LobstersApp(viewModel)
}
}
}
@ -48,14 +43,19 @@ class MainActivity : AppCompatActivity() {
@Composable
fun LobstersApp(
items: List<LobstersPost>,
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)
}

View file

@ -0,0 +1,30 @@
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.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class LobstersViewModel @ViewModelInject constructor(
private val lobstersApi: LobstersApi,
) : ViewModel() {
private var apiPage = 1
private val _posts = MutableStateFlow<List<LobstersPost>>(emptyList())
val posts: StateFlow<List<LobstersPost>> get() = _posts
init {
getMorePosts()
}
fun getMorePosts() {
viewModelScope.launch {
_posts.value += lobstersApi.getHottestPosts(apiPage)
apiPage += 1
}
}
}

View file

@ -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()
}
}

View file

@ -2,9 +2,13 @@
buildscript {
ext {
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'
room_version = '2.3.0-alpha02'
}
repositories {
@ -14,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"
}
}
@ -35,6 +39,7 @@ subprojects {
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
@ -49,6 +54,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'

View file

@ -1,10 +1,12 @@
plugins {
id 'kotlin-android'
id 'kotlin-kapt'
}
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"
}

View file

@ -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<String>? {
return value?.split(SEPARATOR)
}
@TypeConverter
@JvmStatic
fun fromTagList(value: List<String>?): String? {
return value?.joinToString(SEPARATOR)
}
}

View file

@ -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<List<LobstersPost>>
@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()
}

View file

@ -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
}

View file

@ -1,5 +1,4 @@
plugins {
id 'kotlin-android'
id 'kotlin-kapt'
}

View file

@ -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<LobstersPost>
suspend fun getHottestPosts(@Query("page") page: Int): List<LobstersPost>
}

View file

@ -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)
}

View file

@ -1,10 +1,8 @@
plugins {
id 'kotlin-android'
id 'kotlin-kapt'
}
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"

View file

@ -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")