mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-18 05:37:02 +05:30
Merge pull request #31 from msfjarvis/offline-cache
Set up offline caching and no posts state
This commit is contained in:
commit
0ecf21467b
5 changed files with 58 additions and 13 deletions
|
@ -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) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
app/src/main/res/drawable/ic_sync_problem_24px.xml
Normal file
9
app/src/main/res/drawable/ic_sync_problem_24px.xml
Normal 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>
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue