Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions app/src/main/java/dev/androidbroadcast/newssearchapp/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import dev.androidbroadcast.common.AndroidLogcatLogger
import dev.androidbroadcast.common.AppDispatchers
import dev.androidbroadcast.common.Logger
import dev.androidbroadcast.news.database.NewsDatabase
import dev.androidbroadcast.news.database.dao.ArticleDao
import dev.androidbroadcast.newsapi.NewsApi
import okhttp3.OkHttpClient
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
internal object AppModule {
@Provides
@Singleton
fun provideNewsApi(okHttpClient: OkHttpClient?): NewsApi {
Expand All @@ -27,6 +28,18 @@ object AppModule {
)
}

@Provides
@Singleton
fun provideAppCoroutineDispatchers(): AppDispatchers = AppDispatchers()

@Provides
fun provideLogger(): Logger = AndroidLogcatLogger()
}

@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseDao {

@Provides
@Singleton
fun provideNewsDatabase(
Expand All @@ -36,9 +49,9 @@ object AppModule {
}

@Provides
@Singleton
fun provideAppCoroutineDispatchers(): AppDispatchers = AppDispatchers()

@Provides
fun provideLogger(): Logger = AndroidLogcatLogger()
fun provideArticleDao(
database: NewsDatabase
): ArticleDao {
return database.articlesDao
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dev.androidbroadcast.news.data

import androidx.paging.PagingSource
import androidx.paging.PagingState
import dev.androidbroadcast.news.data.model.Article
import dev.androidbroadcast.newsapi.NewsApi
import javax.inject.Inject
import javax.inject.Provider


public class AllArticlesPagingSource(
private val query: String,
private val newsApi: NewsApi,
) : PagingSource<Int, Article>() {

override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}

override suspend fun load(
params: LoadParams<Int>,
): LoadResult<Int, Article> {
val page = params.key ?: 1
val result = newsApi.everything(query, page = page, pageSize = params.loadSize)
.map { articles -> articles.articles.map { it.toArticle(id = counter++) } }
when {
result.isSuccess -> {
val articles: List<Article> = result.getOrThrow()
val prev: Int? = params.key?.let { it - 1 }?.takeIf { it > 0 }
val next: Int? = if (articles.isEmpty()) null else page + 1
return LoadResult.Page(articles, prevKey = prev, nextKey = next)
}

result.isFailure -> return LoadResult.Error(result.exceptionOrNull() ?: UnknownError())
else -> error("Impossible branch")
}
}

public class Factory @Inject constructor(private val newsApi: Provider<NewsApi>) {

public fun newInstance(query: String): PagingSource<Int, Article> {
return AllArticlesPagingSource(query, newsApi.get())
}
}

private companion object {

var counter = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package dev.androidbroadcast.news.data

import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import dev.androidbroadcast.news.database.dao.ArticleDao
import dev.androidbroadcast.news.database.models.ArticleDBO
import dev.androidbroadcast.newsapi.NewsApi
import javax.inject.Inject
import javax.inject.Provider

@ExperimentalPagingApi
public class AllArticlesRemoteMediator internal constructor(
private val query: String,
private val articleDao: ArticleDao,
private val networkService: NewsApi,
) : RemoteMediator<Int, ArticleDBO>() {

override suspend fun load(
loadType: LoadType,
state: PagingState<Int, ArticleDBO>
): MediatorResult {
val pageSize: Int = state.config.pageSize.coerceAtMost(NewsApi.MAX_PAGE_SIZE)

val page: Int = getPage(loadType, state)
?: return MediatorResult.Success(endOfPaginationReached = false)

val networkResult = networkService.everything(query, page = page, pageSize = pageSize)
if (networkResult.isSuccess) {
val totalResults = networkResult.getOrThrow().totalResults
println(totalResults)
val articlesDbo = networkResult.getOrThrow().articles.map { it.toArticleDbo() }
if (loadType == LoadType.REFRESH) {
articleDao.cleanAndInsert(articlesDbo)
} else {
articleDao.insert(articlesDbo)
}

return MediatorResult.Success(
endOfPaginationReached = articlesDbo.size < pageSize
)
}

return MediatorResult.Error(networkResult.exceptionOrNull() ?: UnknownError())
}

private fun getPage(
loadType: LoadType,
state: PagingState<Int, ArticleDBO>
): Int? = when (loadType) {
LoadType.REFRESH ->
state.anchorPosition?.let { state.closestPageToPosition(it) }?.prevKey ?: 1

LoadType.PREPEND -> null

LoadType.APPEND -> {
val lastPage = state.pages.lastOrNull()
if (lastPage == null) {
1
} else {
state.pages.size + 1
}
}
}

public class Factory @Inject constructor(
private val articleDao: Provider<ArticleDao>,
private val networkService: Provider<NewsApi>,
) {

public fun create(query: String): AllArticlesRemoteMediator {
return AllArticlesRemoteMediator(
query = query,
articleDao = articleDao.get(),
networkService = networkService.get()
)
}
}
}
3 changes: 3 additions & 0 deletions database/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ dependencies {
implementation(libs.androidx.core.ktx)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
// Делаю API зависимость чтобы другие модули могли работать с получаемымы результатом из DAO
api(libs.androidx.room.paging)
implementation(libs.androidx.paging.common)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package dev.androidbroadcast.news.database.dao

import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import dev.androidbroadcast.news.database.models.ArticleDBO
import kotlinx.coroutines.flow.Flow

Expand All @@ -12,15 +15,25 @@ interface ArticleDao {
@Query("SELECT * FROM articles")
suspend fun getAll(): List<ArticleDBO>

@Query("SELECT * FROM articles")
fun pagingAll(): PagingSource<Int, ArticleDBO>

@Query("SELECT * FROM articles")
fun observeAll(): Flow<List<ArticleDBO>>

@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(articles: List<ArticleDBO>)

@Delete
suspend fun remove(articles: List<ArticleDBO>)

@Query("DELETE FROM articles")
suspend fun clean()

@Transaction
suspend fun cleanAndInsert(articles: List<ArticleDBO>) {
// Anything inside this method runs in a single transaction.
clean()
insert(articles)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class ArticleDBO(
@ColumnInfo("urlToImage") val urlToImage: String?,
@ColumnInfo("publishedAt") val publishedAt: Date,
@ColumnInfo("content") val content: String,
@PrimaryKey(autoGenerate = true) val id: Long = 0
@PrimaryKey(autoGenerate = true) val id: Int = 0,
)

data class Source(
Expand Down
4 changes: 3 additions & 1 deletion features/news-main/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ dependencies {
compileOnly(libs.androidx.compose.runtime)
api(libs.kotlinx.immutable)

api(project(":news-data"))
api(projects.newsData)
api(projects.database)

implementation(libs.dagger.hilt.android)
kapt(libs.dagger.hilt.compiler)
implementation(libs.androidx.paging.common)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dev.androidbroadcast.news.main

public data class ArticleUI(
val id: Long,
val id: Int,
val title: String,
val description: String,
val imageUrl: String?,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
package dev.androidbroadcast.news.main

import dev.androidbroadcast.news.data.RequestResult
import dev.androidbroadcast.news.data.model.Article
import kotlinx.collections.immutable.toImmutableList

internal fun RequestResult<List<ArticleUI>>.toState(): State {
return when (this) {
is RequestResult.Error -> State.Error(data?.toImmutableList())
is RequestResult.InProgress -> State.Loading(data?.toImmutableList())
is RequestResult.Success -> State.Success(data.toImmutableList())
}
}

internal fun Article.toUiArticle(): ArticleUI {
return ArticleUI(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,51 @@ package dev.androidbroadcast.news.main

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.map
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.androidbroadcast.news.data.ArticlesRepository
import dev.androidbroadcast.news.data.model.Article
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import javax.inject.Provider

@HiltViewModel
public class NewsMainViewModel @Inject internal constructor(
getAllArticlesUseCase: Provider<GetAllArticlesUseCase>
articlesRepository: ArticlesRepository
) : ViewModel() {

public val state: StateFlow<State> =
getAllArticlesUseCase.get().invoke(query = "android")
.map { it.toState() }
.stateIn(viewModelScope, SharingStarted.Lazily, State.None)
private val _query: MutableStateFlow<String> = MutableStateFlow("android")

public val query: StateFlow<String>
get() = _query.asStateFlow()

private val pagingConfig = PagingConfig(
initialLoadSize = 10,
pageSize = 10,
maxSize = 100,
enablePlaceholders = false,
)

public val state: StateFlow<PagingData<ArticleUI>> = query
.map { query ->
articlesRepository.allArticles(
query,
config = pagingConfig
)
}
.flatMapConcat { pagingDataFlow ->
pagingDataFlow.map { pagingData ->
pagingData.map(Article::toUiArticle)
}
}
.stateIn(viewModelScope, SharingStarted.Lazily, PagingData.empty())
}

This file was deleted.

1 change: 1 addition & 0 deletions features/news-main/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@ dependencies {

implementation(libs.coil.compose)
implementation(projects.newsCommon)
implementation(libs.androidx.paging.compose)
}
Loading