Skip to content

Commit

Permalink
fix: crash when restoring the app after a long period of inactivity
Browse files Browse the repository at this point in the history
  • Loading branch information
dixidroid committed May 13, 2024
1 parent 223fc43 commit bc43dcc
Show file tree
Hide file tree
Showing 22 changed files with 319 additions and 276 deletions.
25 changes: 21 additions & 4 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,23 @@ val screenModule = module {
viewModel { (qualityType: String) -> VideoQualityViewModel(qualityType, get(), get(), get()) }
viewModel { DeleteProfileViewModel(get(), get(), get(), get(), get()) }
viewModel { (username: String) -> AnothersProfileViewModel(get(), get(), username) }
viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel {
SettingsViewModel(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
viewModel { ManageAccountViewModel(get(), get(), get(), get(), get()) }

single { CourseRepository(get(), get(), get(), get()) }
single { CourseRepository(get(), get(), get(), get(), get()) }
factory { CourseInteractor(get()) }
viewModel { (pathId: String, infoType: String) ->
CourseInfoViewModel(
Expand Down Expand Up @@ -279,8 +292,10 @@ val screenModule = module {
get(),
)
}
viewModel { (enrollmentMode: String) ->
viewModel { (courseId: String, courseTitle: String, enrollmentMode: String) ->
CourseDatesViewModel(
courseId,
courseTitle,
enrollmentMode,
get(),
get(),
Expand All @@ -305,8 +320,10 @@ val screenModule = module {

single { DiscussionRepository(get(), get(), get()) }
factory { DiscussionInteractor(get()) }
viewModel {
viewModel { (courseId: String, courseTitle: String) ->
DiscussionTopicsViewModel(
courseId,
courseTitle,
get(),
get(),
get(),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ class CourseNotifier {
suspend fun send(event: CalendarSyncEvent) = channel.emit(event)
suspend fun send(event: CourseDatesShifted) = channel.emit(event)
suspend fun send(event: CourseLoading) = channel.emit(event)
suspend fun send(event: CourseDataReady) = channel.emit(event)
suspend fun send(event: CourseRefresh) = channel.emit(event)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import org.openedx.core.ApiConstants
import org.openedx.core.data.api.CourseApi
import org.openedx.core.data.model.BlocksCompletionBody
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.*
import org.openedx.core.domain.model.CourseComponentStatus
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.exception.NoCachedDataException
import org.openedx.core.module.db.DownloadDao
import org.openedx.core.system.connection.NetworkConnection
import org.openedx.course.data.storage.CourseDao

class CourseRepository(
private val api: CourseApi,
private val courseDao: CourseDao,
private val downloadDao: DownloadDao,
private val preferencesManager: CorePreferences,
private val networkConnection: NetworkConnection,
) {
private var courseStructure: CourseStructure? = null
private var courseStructure = mutableMapOf<String, CourseStructure>()

suspend fun removeDownloadModel(id: String) {
downloadDao.removeDownloadModel(id)
Expand All @@ -26,35 +29,33 @@ class CourseRepository(
list.map { it.mapToDomain() }
}

suspend fun preloadCourseStructure(courseId: String) {
val response = api.getCourseStructure(
"stale-if-error=0",
"v3",
preferencesManager.user?.username,
courseId
)
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
courseStructure = null
courseStructure = response.mapToDomain()
fun hasCourses(courseId: String): Boolean {
return courseStructure[courseId] != null
}

suspend fun preloadCourseStructureFromCache(courseId: String) {
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
courseStructure = null
if (cachedCourseStructure != null) {
courseStructure = cachedCourseStructure.mapToDomain()
} else {
throw NoCachedDataException()
}
}
suspend fun getCourseStructure(courseId: String, isNeedRefresh: Boolean): CourseStructure {
if (!isNeedRefresh) courseStructure[courseId]?.let { return it }

if (networkConnection.isOnline()) {
val response = api.getCourseStructure(
"stale-if-error=0",
"v3",
preferencesManager.user?.username,
courseId
)
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
courseStructure[courseId] = response.mapToDomain()

@Throws(IllegalStateException::class)
fun getCourseStructureFromCache(): CourseStructure {
if (courseStructure != null) {
return courseStructure!!
} else {
throw IllegalStateException("Course structure is empty")
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
if (cachedCourseStructure != null) {
courseStructure[courseId] = cachedCourseStructure.mapToDomain()
} else {
throw NoCachedDataException()
}
}

return courseStructure[courseId]!!
}

suspend fun getCourseStatus(courseId: String): CourseComponentStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ class CourseInteractor(
private val repository: CourseRepository
) {

suspend fun preloadCourseStructure(courseId: String) =
repository.preloadCourseStructure(courseId)

suspend fun preloadCourseStructureFromCache(courseId: String) =
repository.preloadCourseStructureFromCache(courseId)

@Throws(IllegalStateException::class)
fun getCourseStructureFromCache() = repository.getCourseStructureFromCache()
suspend fun getCourseStructure(
courseId: String,
isNeedRefresh: Boolean = false
): CourseStructure {
return repository.getCourseStructure(courseId, isNeedRefresh)
}

@Throws(IllegalStateException::class)
fun getCourseStructureForVideos(): CourseStructure {
val courseStructure = repository.getCourseStructureFromCache()
suspend fun getCourseStructureForVideos(
courseId: String,
isNeedRefresh: Boolean = false
): CourseStructure {
val courseStructure = repository.getCourseStructure(courseId, isNeedRefresh)
val blocks = courseStructure.blockData
val videoBlocks = blocks.filter { it.type == BlockType.VIDEO }
val resultBlocks = ArrayList<Block>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand Down Expand Up @@ -294,6 +295,8 @@ fun CourseDashboard(
val refreshing by viewModel.refreshing.collectAsState(true)
val courseImage by viewModel.courseImage.collectAsState()
val uiMessage by viewModel.uiMessage.collectAsState(null)
val dataReady = viewModel.dataReady.observeAsState()

val pagerState = rememberPagerState(pageCount = { CourseContainerTab.entries.size })
val tabState = rememberLazyListState()
val snackState = remember { SnackbarHostState() }
Expand Down Expand Up @@ -351,15 +354,17 @@ fun CourseDashboard(
fragmentManager.popBackStack()
},
bodyContent = {
DashboardPager(
windowSize = windowSize,
viewModel = viewModel,
pagerState = pagerState,
isNavigationEnabled = isNavigationEnabled,
isResumed = isResumed,
fragmentManager = fragmentManager,
bundle = bundle
)
if (dataReady.value == true) {
DashboardPager(
windowSize = windowSize,
viewModel = viewModel,
pagerState = pagerState,
isNavigationEnabled = isNavigationEnabled,
isResumed = isResumed,
fragmentManager = fragmentManager,
bundle = bundle
)
}
}
)
PullRefreshIndicator(
Expand Down Expand Up @@ -462,6 +467,8 @@ fun DashboardPager(
courseDatesViewModel = koinViewModel(
parameters = {
parametersOf(
bundle.getString(CourseContainerFragment.ARG_COURSE_ID, ""),
bundle.getString(CourseContainerFragment.ARG_TITLE, ""),
bundle.getString(CourseContainerFragment.ARG_ENROLLMENT_MODE, "")
)
}
Expand All @@ -478,6 +485,14 @@ fun DashboardPager(

CourseContainerTab.DISCUSSIONS -> {
DiscussionTopicsScreen(
discussionTopicsViewModel = koinViewModel(
parameters = {
parametersOf(
bundle.getString(CourseContainerFragment.ARG_COURSE_ID, ""),
bundle.getString(CourseContainerFragment.ARG_TITLE, ""),
)
}
),
windowSize = windowSize,
fragmentManager = fragmentManager
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import org.openedx.core.system.connection.NetworkConnection
import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
import org.openedx.core.system.notifier.CourseCompletionSet
import org.openedx.core.system.notifier.CourseDataReady
import org.openedx.core.system.notifier.CourseDatesShifted
import org.openedx.core.system.notifier.CourseLoading
import org.openedx.core.system.notifier.CourseNotifier
Expand Down Expand Up @@ -169,12 +168,7 @@ class CourseContainerViewModel(
_showProgress.value = true
viewModelScope.launch {
try {
if (networkConnection.isOnline()) {
interactor.preloadCourseStructure(courseId)
} else {
interactor.preloadCourseStructureFromCache(courseId)
}
val courseStructure = interactor.getCourseStructureFromCache()
val courseStructure = interactor.getCourseStructure(courseId)
courseName = courseStructure.name
_organization = courseStructure.org
_isSelfPaced = courseStructure.isSelfPaced
Expand All @@ -183,7 +177,6 @@ class CourseContainerViewModel(
val isReady = start < Date()
if (isReady) {
_isNavigationEnabled.value = true
courseNotifier.send(CourseDataReady(courseStructure))
}
isReady
}
Expand Down Expand Up @@ -248,7 +241,7 @@ class CourseContainerViewModel(
fun updateData() {
viewModelScope.launch {
try {
interactor.preloadCourseStructure(courseId)
interactor.getCourseStructure(courseId, isNeedRefresh = true)
} catch (e: Exception) {
if (e.isInternetError()) {
_errorMessage.value =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.CourseBannerType
import org.openedx.core.domain.model.CourseDateBlock
import org.openedx.core.domain.model.CourseStructure
import org.openedx.core.extension.getSequentialBlocks
import org.openedx.core.extension.getVerticalBlocks
import org.openedx.core.extension.isInternetError
import org.openedx.core.presentation.course.CourseContainerTab
import org.openedx.core.system.ResourceManager
import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
import org.openedx.core.system.notifier.CourseDataReady
import org.openedx.core.system.notifier.CourseDatesShifted
import org.openedx.core.system.notifier.CourseLoading
import org.openedx.core.system.notifier.CourseNotifier
Expand All @@ -41,6 +41,8 @@ import org.openedx.course.presentation.calendarsync.CalendarSyncUIState
import org.openedx.core.R as CoreR

class CourseDatesViewModel(
val courseId: String,
courseTitle: String,
private val enrollmentMode: String,
private val courseNotifier: CourseNotifier,
private val interactor: CourseInteractor,
Expand All @@ -51,8 +53,6 @@ class CourseDatesViewModel(
private val config: Config,
) : BaseViewModel() {

var courseId = ""
var courseName = ""
var isSelfPaced = true

private val _uiState = MutableLiveData<DatesUIState>(DatesUIState.Loading)
Expand All @@ -66,14 +66,15 @@ class CourseDatesViewModel(
private val _calendarSyncUIState = MutableStateFlow(
CalendarSyncUIState(
isCalendarSyncEnabled = isCalendarSyncEnabled(),
calendarTitle = calendarManager.getCourseCalendarTitle(courseName),
calendarTitle = calendarManager.getCourseCalendarTitle(courseTitle),
isSynced = false,
)
)
val calendarSyncUIState: StateFlow<CalendarSyncUIState> =
_calendarSyncUIState.asStateFlow()

private var courseBannerType: CourseBannerType = CourseBannerType.BLANK
private var courseStructure: CourseStructure? = null

val isCourseExpandableSectionsEnabled get() = config.isCourseNestedListEnabled()

Expand All @@ -90,22 +91,19 @@ class CourseDatesViewModel(
loadingCourseDatesInternal()
}
}

is CourseDataReady -> {
courseId = event.courseStructure.id
courseName = event.courseStructure.name
isSelfPaced = event.courseStructure.isSelfPaced
loadingCourseDatesInternal()
updateAndFetchCalendarSyncState()
}
}
}
}

loadingCourseDatesInternal()
updateAndFetchCalendarSyncState()
}

private fun loadingCourseDatesInternal() {
viewModelScope.launch {
try {
courseStructure = interactor.getCourseStructure(courseId = courseId)
isSelfPaced = courseStructure?.isSelfPaced ?: false
val datesResponse = interactor.getCourseDates(courseId = courseId)
if (datesResponse.datesSection.isEmpty()) {
_uiState.value = DatesUIState.Empty
Expand Down Expand Up @@ -146,18 +144,17 @@ class CourseDatesViewModel(

fun getVerticalBlock(blockId: String): Block? {
return try {
val courseStructure = interactor.getCourseStructureFromCache()
courseStructure.blockData.getVerticalBlocks().find { it.descendants.contains(blockId) }
courseStructure?.blockData?.getVerticalBlocks()
?.find { it.descendants.contains(blockId) }
} catch (e: Exception) {
null
}
}

fun getSequentialBlock(blockId: String): Block? {
return try {
val courseStructure = interactor.getCourseStructureFromCache()
courseStructure.blockData.getSequentialBlocks()
.find { it.descendants.contains(blockId) }
courseStructure?.blockData?.getSequentialBlocks()
?.find { it.descendants.contains(blockId) }
} catch (e: Exception) {
null
}
Expand Down
Loading

0 comments on commit bc43dcc

Please sign in to comment.