Skip to content

Commit

Permalink
feat: implement background fetch worker (cont.)
Browse files Browse the repository at this point in the history
  • Loading branch information
kabirnayeem99 committed Jan 26, 2024
1 parent 7178aae commit b4c39cb
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 26 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ android {

defaultConfig {
applicationId "io.github.kabirnayeem99.islamqaorg"
minSdk 26
minSdk 29
targetSdk 34
versionCode 1
versionName "1.2.1-alpha"
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

<application
android:name=".common.base.IslamQaApp"
Expand All @@ -31,6 +32,11 @@
android:exported="true"
android:fitsSystemWindows="true" />

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
tools:node="merge" />

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface QuestionListDao {
* Returns a list of QuestionEntity objects, ordered by timeInMillis in descending order, and
* limited to 10 items
*/
@Query("SELECT * FROM questionentity ORDER BY timeInMillis desc LIMIT 10")
@Query("SELECT * FROM questionentity ORDER BY RANDOM() desc LIMIT 10")
suspend fun getRandomQuestions(): List<QuestionEntity>

// @Query("SELECT * FROM questionentity WHERE question LIKE '%' || :query || '%' ORDER BY timeInMillis desc LIMIT 10")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ class ScrapingService {
fiqh = getFiqhForQuestionDetails(),
source = getSourceForQuestionDetails(),
originalLink = link,
nextQuestionLink = getPrevOrNextLink(Page.NEXT),
previousQuestionLink = getPrevOrNextLink(Page.PREV),
relevantQuestions = getRelevantQuestionsFromQuestionDetails()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import io.github.kabirnayeem99.islamqaorg.domain.entity.Fiqh
import io.github.kabirnayeem99.islamqaorg.domain.entity.Question
import io.github.kabirnayeem99.islamqaorg.domain.entity.QuestionDetail
import io.github.kabirnayeem99.islamqaorg.domain.repository.QuestionAnswerRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import javax.inject.Inject
import kotlin.random.Random

class QuestionAnswerRepositoryImpl
@Inject constructor(
Expand All @@ -34,27 +39,19 @@ class QuestionAnswerRepositoryImpl
private var inMemoryRandomQuestionList = emptyList<Question>()

override suspend fun getRandomQuestionList(shouldRefresh: Boolean): Flow<List<Question>> {
val cachedRandomQuestionList = inMemoryMutex.withLock { inMemoryRandomQuestionList }
return flow {
if (isNetworkAvailable) {
val remoteData = getRandomQuestionListFromRemoteDataSource()
emit(remoteData)
} else {
val localQuestionList = localDataSource.getRandomQuestionList()
if (localQuestionList.isEmpty()) {
val remoteData = getRandomQuestionListFromRemoteDataSource()
emit(remoteData)
}
inMemoryMutex.withLock { inMemoryRandomQuestionList = localQuestionList }
preferenceDataSource.updateNeedingToRefresh()
emit(localQuestionList)
}
val localQuestionList = localDataSource.getRandomQuestionList()
inMemoryRandomQuestionList = localQuestionList
emit(localQuestionList)
}.onStart {
if (cachedRandomQuestionList.isNotEmpty()) emit(cachedRandomQuestionList)
}
if (inMemoryRandomQuestionList.isNotEmpty()) emit(inMemoryRandomQuestionList)
}.catch { e ->
Timber.e(e, "getRandomQuestionList: ")
emit(inMemoryFiqhBasedQuestionList)
}.flowOn(Dispatchers.IO)
}

override suspend fun fetchAndSaveRandomQuestionList(): Boolean {
override suspend fun fetchAndSaveQuestionListByFiqh(setProgress: suspend (Int) -> Unit): Boolean {
try {
val fiqh = getCurrentlySelectedFiqh()
val lastSyncedPage = preferenceDataSource.getCurrentFiqhLastPageSynced()
Expand All @@ -66,6 +63,11 @@ class QuestionAnswerRepositoryImpl
throw IllegalStateException("Failed with page $page for fiqh ${fiqh.displayName}")
} else {
localDataSource.cacheQuestionList(list)
list.forEachIndexed { index, q ->
delay(Random.nextLong(q.url.length.toLong()))
getAndCacheQuestionDetailFromRemote(q.url)
setProgress(((page + 1) - newStartingPage) * (index + 1))
}
preferenceDataSource.saveCurrentFiqhLastPageSynced(newLastSyncingPage)
Timber.i("fetchAndSaveRandomQuestionList: saved page $page for fiqh ${fiqh.displayName}")
}
Expand Down Expand Up @@ -95,7 +97,6 @@ class QuestionAnswerRepositoryImpl

override suspend fun getQuestionDetails(url: String): Flow<QuestionDetail> {
val inMemoryQuestionDetail = inMemoryQuestionDetails.get(url) ?: QuestionDetail()

return flow {
try {
val questionDetailLocal = localDataSource.getDetailedQuestionAndAnswer(url)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package io.github.kabirnayeem99.islamqaorg.data.workers

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.ServiceInfo
import androidx.core.app.NotificationCompat
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
Expand All @@ -12,6 +17,11 @@ import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.github.kabirnayeem99.islamqaorg.R
import io.github.kabirnayeem99.islamqaorg.data.dataSource.workers.CHANNEL_ID
import io.github.kabirnayeem99.islamqaorg.data.dataSource.workers.NOTIFICATION_ID
import io.github.kabirnayeem99.islamqaorg.data.dataSource.workers.SYNC_NOTIFICATION_CHANNEL_DESCRIPTION
import io.github.kabirnayeem99.islamqaorg.data.dataSource.workers.SYNC_NOTIFICATION_CHANNEL_NAME
import io.github.kabirnayeem99.islamqaorg.domain.repository.QuestionAnswerRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand All @@ -24,17 +34,53 @@ class BackgroundQAListFetcherWorker @AssistedInject constructor(
@Assisted private val workerParams: WorkerParameters,
private val questionAnswerRepository: QuestionAnswerRepository,
) : CoroutineWorker(appContext, workerParams) {

override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
val successfullySynced = questionAnswerRepository.fetchAndSaveRandomQuestionList()
createChannel()
setForeground(createForegroundInfo(1))
val successfullySynced = questionAnswerRepository.fetchAndSaveQuestionListByFiqh { p ->
setForeground(createForegroundInfo(p))
}
if (successfullySynced) Result.success() else Result.failure()
}
}


private fun createForegroundInfo(progress: Int): ForegroundInfo {
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
val intent = WorkManager.getInstance(applicationContext).createCancelPendingIntent(id)
val progressText = "Loading questions: ${progress}%"
val notification =
NotificationCompat.Builder(applicationContext, CHANNEL_ID).setContentTitle(title)
.setTicker(title).setContentText(progressText).setSmallIcon(R.drawable.ic_islamqa)
.setOngoing(true).addAction(R.drawable.ic_arrow_forward, cancel, intent)
.setProgress(100, progress, false)
.build()
return ForegroundInfo(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
}

private fun createChannel() {
val name = SYNC_NOTIFICATION_CHANNEL_NAME
val description = SYNC_NOTIFICATION_CHANNEL_DESCRIPTION
val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(CHANNEL_ID, name, importance)
channel.description = description
val notificationManager =
appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
notificationManager?.createNotificationChannel(channel)
}


companion object {
private const val TAG = "BackgroundQAListFetcher"
private const val SYNC_INTERVAL_HOURS = 4L
fun enqueue(workManager: WorkManager) {
fun enqueue(workManager: WorkManager) {
try {
val constraintsBuilder = Constraints.Builder()
constraintsBuilder.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface QuestionAnswerRepository {

suspend fun getRandomQuestionList(shouldRefresh: Boolean = false): Flow<List<Question>>

suspend fun fetchAndSaveRandomQuestionList(): Boolean
suspend fun fetchAndSaveQuestionListByFiqh(setProgress: suspend (Int) -> Unit = {}): Boolean

suspend fun getFiqhBasedQuestionList(
pageNumber: Int, shouldRefresh: Boolean = false
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-night/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="notification_title">Syncing questions and answers</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-v31/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="notification_title">Syncing questions and answers</string>
</resources>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@
<string name="label_syncing">We are syncing the questions and answers from IslamQA.org in the background. It will ensure faster loading, the next time you enter this app. It will also reduce load on the original website.</string>
<string name="label_synced">We are done syncing. Note that, this app is just a frontend for IslamQA.org. Please, pray for the creator of this awesome website for their amazing work. Visit this website for any of your query.</string>
<string name="content_desc_search">Search for questions</string>
<string name="notification_title">Syncing questions and answers</string>
<string name="cancel_download">Cancel syncing</string>
</resources>

0 comments on commit b4c39cb

Please sign in to comment.