Merge pull request #31 from msfjarvis/offline-cache

Set up offline caching and no posts state
This commit is contained in:
probot-auto-merge[bot] 2020-09-27 11:44:40 +00:00 committed by GitHub
commit 0ecf21467b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 13 deletions

View file

@ -4,6 +4,9 @@ import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Text import androidx.compose.foundation.Text
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumnForIndexed import androidx.compose.foundation.lazy.LazyColumnForIndexed
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar
@ -11,10 +14,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Providers import androidx.compose.runtime.Providers
import androidx.compose.runtime.ambientOf import androidx.compose.runtime.ambientOf
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.setContent import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.lobsters.api.LobstersApi import dev.msfjarvis.lobsters.api.LobstersApi
import dev.msfjarvis.lobsters.compose.utils.IconResource
import dev.msfjarvis.lobsters.data.LobstersViewModel import dev.msfjarvis.lobsters.data.LobstersViewModel
import dev.msfjarvis.lobsters.ui.LobstersItem import dev.msfjarvis.lobsters.ui.LobstersItem
import dev.msfjarvis.lobsters.ui.LobstersTheme import dev.msfjarvis.lobsters.ui.LobstersTheme
@ -52,15 +58,26 @@ fun LobstersApp(
Scaffold( Scaffold(
topBar = { TopAppBar({ Text(text = stringResource(R.string.app_name)) }) }, topBar = { TopAppBar({ Text(text = stringResource(R.string.app_name)) }) },
bodyContent = { bodyContent = {
LazyColumnForIndexed(state.value) { index, item -> if (state.value.isEmpty()) {
if (lastIndex == index) { Column(
viewModel.getMorePosts() modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconResource(R.drawable.ic_sync_problem_24px)
Text(stringResource(R.string.nothing_to_see_here))
}
} else {
LazyColumnForIndexed(state.value) { index, item ->
if (lastIndex == index) {
viewModel.getMorePosts()
}
LobstersItem(
item,
linkOpenAction = { post -> urlLauncher.launch(post.url) },
commentOpenAction = { post -> urlLauncher.launch(post.commentsUrl) },
)
} }
LobstersItem(
item,
linkOpenAction = { post -> urlLauncher.launch(post.url) },
commentOpenAction = { post -> urlLauncher.launch(post.commentsUrl) },
)
} }
} }
) )

View file

@ -4,27 +4,44 @@ import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dev.msfjarvis.lobsters.api.LobstersApi import dev.msfjarvis.lobsters.api.LobstersApi
import dev.msfjarvis.lobsters.data.source.PostsDatabase
import dev.msfjarvis.lobsters.model.LobstersPost import dev.msfjarvis.lobsters.model.LobstersPost
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.net.UnknownHostException
class LobstersViewModel @ViewModelInject constructor( class LobstersViewModel @ViewModelInject constructor(
private val lobstersApi: LobstersApi, private val lobstersApi: LobstersApi,
database: PostsDatabase,
) : ViewModel() { ) : ViewModel() {
private var apiPage = 1 private var apiPage = 1
private val _posts = MutableStateFlow<List<LobstersPost>>(emptyList()) private val _posts = MutableStateFlow<List<LobstersPost>>(emptyList())
private val dao = database.postsDao()
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
when (throwable) {
// Swallow UHE since that happens when there is no internet and we'll just rely on our cache
is UnknownHostException -> {}
else -> throw throwable
}
}
val posts: StateFlow<List<LobstersPost>> get() = _posts val posts: StateFlow<List<LobstersPost>> get() = _posts
init { init {
viewModelScope.launch {
dao.loadPosts().collectLatest { _posts.value = it }
}
getMorePosts() getMorePosts()
} }
fun getMorePosts() { fun getMorePosts() {
viewModelScope.launch { viewModelScope.launch(coroutineExceptionHandler) {
_posts.value += lobstersApi.getHottestPosts(apiPage) val newPosts = lobstersApi.getHottestPosts(apiPage)
_posts.value += newPosts
apiPage += 1 apiPage += 1
dao.insertPosts(*_posts.value.toTypedArray())
} }
} }
} }

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3,12c0,2.21 0.91,4.2 2.36,5.64l-1.51,1.51c-0.31,0.31 -0.09,0.85 0.36,0.85L8.5,20c0.28,0 0.5,-0.22 0.5,-0.5v-4.29c0,-0.45 -0.54,-0.67 -0.85,-0.35l-1.39,1.39C5.68,15.15 5,13.66 5,12c0,-2.39 1.4,-4.46 3.43,-5.42 0.34,-0.16 0.57,-0.47 0.57,-0.84v-0.19c0,-0.68 -0.71,-1.11 -1.32,-0.82C4.92,5.99 3,8.77 3,12zM11,17h2v-2h-2v2zM19.79,4L15.5,4c-0.28,0 -0.5,0.22 -0.5,0.5v4.29c0,0.45 0.54,0.67 0.85,0.35l1.39,-1.39C18.32,8.85 19,10.34 19,12c0,2.39 -1.4,4.46 -3.43,5.42 -0.34,0.16 -0.57,0.47 -0.57,0.84v0.18c0,0.68 0.71,1.11 1.32,0.82C19.08,18.01 21,15.23 21,12c0,-2.21 -0.91,-4.2 -2.36,-5.64l1.51,-1.51c0.31,-0.31 0.09,-0.85 -0.36,-0.85zM12,13c0.55,0 1,-0.45 1,-1L13,8c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v4c0,0.55 0.45,1 1,1z"/>
</vector>

View file

@ -1,3 +1,4 @@
<resources> <resources>
<string name="app_name">lobste.rs</string> <string name="app_name">lobste.rs</string>
</resources> <string name="nothing_to_see_here">Nothing to see here…</string>
</resources>

View file

@ -3,6 +3,7 @@ package dev.msfjarvis.lobsters.data.source
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import dev.msfjarvis.lobsters.data.model.LobstersEntity import dev.msfjarvis.lobsters.data.model.LobstersEntity
@ -19,7 +20,7 @@ abstract class PostsDao {
insertPosts(posts.map { LobstersEntity(it) }) insertPosts(posts.map { LobstersEntity(it) })
} }
@Insert @Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract suspend fun insertPosts(posts: List<LobstersEntity>) protected abstract suspend fun insertPosts(posts: List<LobstersEntity>)
@Transaction @Transaction