Skip to content

Commit ae777dd

Browse files
Merge pull request #290 from touchapp/fix/crash_when_restore
fix: crash when restoring the app after a long period of inactivity
2 parents 223fc43 + 498f0ef commit ae777dd

File tree

22 files changed

+319
-276
lines changed

22 files changed

+319
-276
lines changed

app/src/main/java/org/openedx/app/di/ScreenModule.kt

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,23 @@ val screenModule = module {
148148
viewModel { (qualityType: String) -> VideoQualityViewModel(qualityType, get(), get(), get()) }
149149
viewModel { DeleteProfileViewModel(get(), get(), get(), get(), get()) }
150150
viewModel { (username: String) -> AnothersProfileViewModel(get(), get(), username) }
151-
viewModel { SettingsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
151+
viewModel {
152+
SettingsViewModel(
153+
get(),
154+
get(),
155+
get(),
156+
get(),
157+
get(),
158+
get(),
159+
get(),
160+
get(),
161+
get(),
162+
get()
163+
)
164+
}
152165
viewModel { ManageAccountViewModel(get(), get(), get(), get(), get()) }
153166

154-
single { CourseRepository(get(), get(), get(), get()) }
167+
single { CourseRepository(get(), get(), get(), get(), get()) }
155168
factory { CourseInteractor(get()) }
156169
viewModel { (pathId: String, infoType: String) ->
157170
CourseInfoViewModel(
@@ -279,8 +292,10 @@ val screenModule = module {
279292
get(),
280293
)
281294
}
282-
viewModel { (enrollmentMode: String) ->
295+
viewModel { (courseId: String, courseTitle: String, enrollmentMode: String) ->
283296
CourseDatesViewModel(
297+
courseId,
298+
courseTitle,
284299
enrollmentMode,
285300
get(),
286301
get(),
@@ -305,8 +320,10 @@ val screenModule = module {
305320

306321
single { DiscussionRepository(get(), get(), get()) }
307322
factory { DiscussionInteractor(get()) }
308-
viewModel {
323+
viewModel { (courseId: String, courseTitle: String) ->
309324
DiscussionTopicsViewModel(
325+
courseId,
326+
courseTitle,
310327
get(),
311328
get(),
312329
get(),

core/src/main/java/org/openedx/core/system/notifier/CourseDataReady.kt

Lines changed: 0 additions & 5 deletions
This file was deleted.

core/src/main/java/org/openedx/core/system/notifier/CourseNotifier.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@ class CourseNotifier {
1818
suspend fun send(event: CalendarSyncEvent) = channel.emit(event)
1919
suspend fun send(event: CourseDatesShifted) = channel.emit(event)
2020
suspend fun send(event: CourseLoading) = channel.emit(event)
21-
suspend fun send(event: CourseDataReady) = channel.emit(event)
2221
suspend fun send(event: CourseRefresh) = channel.emit(event)
2322
}

course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,21 @@ import org.openedx.core.ApiConstants
55
import org.openedx.core.data.api.CourseApi
66
import org.openedx.core.data.model.BlocksCompletionBody
77
import org.openedx.core.data.storage.CorePreferences
8-
import org.openedx.core.domain.model.*
8+
import org.openedx.core.domain.model.CourseComponentStatus
9+
import org.openedx.core.domain.model.CourseStructure
910
import org.openedx.core.exception.NoCachedDataException
1011
import org.openedx.core.module.db.DownloadDao
12+
import org.openedx.core.system.connection.NetworkConnection
1113
import org.openedx.course.data.storage.CourseDao
1214

1315
class CourseRepository(
1416
private val api: CourseApi,
1517
private val courseDao: CourseDao,
1618
private val downloadDao: DownloadDao,
1719
private val preferencesManager: CorePreferences,
20+
private val networkConnection: NetworkConnection,
1821
) {
19-
private var courseStructure: CourseStructure? = null
22+
private var courseStructure = mutableMapOf<String, CourseStructure>()
2023

2124
suspend fun removeDownloadModel(id: String) {
2225
downloadDao.removeDownloadModel(id)
@@ -26,35 +29,33 @@ class CourseRepository(
2629
list.map { it.mapToDomain() }
2730
}
2831

29-
suspend fun preloadCourseStructure(courseId: String) {
30-
val response = api.getCourseStructure(
31-
"stale-if-error=0",
32-
"v3",
33-
preferencesManager.user?.username,
34-
courseId
35-
)
36-
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
37-
courseStructure = null
38-
courseStructure = response.mapToDomain()
32+
fun hasCourses(courseId: String): Boolean {
33+
return courseStructure[courseId] != null
3934
}
4035

41-
suspend fun preloadCourseStructureFromCache(courseId: String) {
42-
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
43-
courseStructure = null
44-
if (cachedCourseStructure != null) {
45-
courseStructure = cachedCourseStructure.mapToDomain()
46-
} else {
47-
throw NoCachedDataException()
48-
}
49-
}
36+
suspend fun getCourseStructure(courseId: String, isNeedRefresh: Boolean): CourseStructure {
37+
if (!isNeedRefresh) courseStructure[courseId]?.let { return it }
38+
39+
if (networkConnection.isOnline()) {
40+
val response = api.getCourseStructure(
41+
"stale-if-error=0",
42+
"v3",
43+
preferencesManager.user?.username,
44+
courseId
45+
)
46+
courseDao.insertCourseStructureEntity(response.mapToRoomEntity())
47+
courseStructure[courseId] = response.mapToDomain()
5048

51-
@Throws(IllegalStateException::class)
52-
fun getCourseStructureFromCache(): CourseStructure {
53-
if (courseStructure != null) {
54-
return courseStructure!!
5549
} else {
56-
throw IllegalStateException("Course structure is empty")
50+
val cachedCourseStructure = courseDao.getCourseStructureById(courseId)
51+
if (cachedCourseStructure != null) {
52+
courseStructure[courseId] = cachedCourseStructure.mapToDomain()
53+
} else {
54+
throw NoCachedDataException()
55+
}
5756
}
57+
58+
return courseStructure[courseId]!!
5859
}
5960

6061
suspend fun getCourseStatus(courseId: String): CourseComponentStatus {

course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ class CourseInteractor(
99
private val repository: CourseRepository
1010
) {
1111

12-
suspend fun preloadCourseStructure(courseId: String) =
13-
repository.preloadCourseStructure(courseId)
14-
15-
suspend fun preloadCourseStructureFromCache(courseId: String) =
16-
repository.preloadCourseStructureFromCache(courseId)
17-
18-
@Throws(IllegalStateException::class)
19-
fun getCourseStructureFromCache() = repository.getCourseStructureFromCache()
12+
suspend fun getCourseStructure(
13+
courseId: String,
14+
isNeedRefresh: Boolean = false
15+
): CourseStructure {
16+
return repository.getCourseStructure(courseId, isNeedRefresh)
17+
}
2018

21-
@Throws(IllegalStateException::class)
22-
fun getCourseStructureForVideos(): CourseStructure {
23-
val courseStructure = repository.getCourseStructureFromCache()
19+
suspend fun getCourseStructureForVideos(
20+
courseId: String,
21+
isNeedRefresh: Boolean = false
22+
): CourseStructure {
23+
val courseStructure = repository.getCourseStructure(courseId, isNeedRefresh)
2424
val blocks = courseStructure.blockData
2525
val videoBlocks = blocks.filter { it.type == BlockType.VIDEO }
2626
val resultBlocks = ArrayList<Block>()

course/src/main/java/org/openedx/course/presentation/container/CourseContainerFragment.kt

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable
3030
import androidx.compose.runtime.LaunchedEffect
3131
import androidx.compose.runtime.collectAsState
3232
import androidx.compose.runtime.getValue
33+
import androidx.compose.runtime.livedata.observeAsState
3334
import androidx.compose.runtime.mutableStateOf
3435
import androidx.compose.runtime.remember
3536
import androidx.compose.runtime.rememberCoroutineScope
@@ -294,6 +295,8 @@ fun CourseDashboard(
294295
val refreshing by viewModel.refreshing.collectAsState(true)
295296
val courseImage by viewModel.courseImage.collectAsState()
296297
val uiMessage by viewModel.uiMessage.collectAsState(null)
298+
val dataReady = viewModel.dataReady.observeAsState()
299+
297300
val pagerState = rememberPagerState(pageCount = { CourseContainerTab.entries.size })
298301
val tabState = rememberLazyListState()
299302
val snackState = remember { SnackbarHostState() }
@@ -351,15 +354,17 @@ fun CourseDashboard(
351354
fragmentManager.popBackStack()
352355
},
353356
bodyContent = {
354-
DashboardPager(
355-
windowSize = windowSize,
356-
viewModel = viewModel,
357-
pagerState = pagerState,
358-
isNavigationEnabled = isNavigationEnabled,
359-
isResumed = isResumed,
360-
fragmentManager = fragmentManager,
361-
bundle = bundle
362-
)
357+
if (dataReady.value == true) {
358+
DashboardPager(
359+
windowSize = windowSize,
360+
viewModel = viewModel,
361+
pagerState = pagerState,
362+
isNavigationEnabled = isNavigationEnabled,
363+
isResumed = isResumed,
364+
fragmentManager = fragmentManager,
365+
bundle = bundle
366+
)
367+
}
363368
}
364369
)
365370
PullRefreshIndicator(
@@ -462,6 +467,8 @@ fun DashboardPager(
462467
courseDatesViewModel = koinViewModel(
463468
parameters = {
464469
parametersOf(
470+
bundle.getString(CourseContainerFragment.ARG_COURSE_ID, ""),
471+
bundle.getString(CourseContainerFragment.ARG_TITLE, ""),
465472
bundle.getString(CourseContainerFragment.ARG_ENROLLMENT_MODE, "")
466473
)
467474
}
@@ -478,6 +485,14 @@ fun DashboardPager(
478485

479486
CourseContainerTab.DISCUSSIONS -> {
480487
DiscussionTopicsScreen(
488+
discussionTopicsViewModel = koinViewModel(
489+
parameters = {
490+
parametersOf(
491+
bundle.getString(CourseContainerFragment.ARG_COURSE_ID, ""),
492+
bundle.getString(CourseContainerFragment.ARG_TITLE, ""),
493+
)
494+
}
495+
),
481496
windowSize = windowSize,
482497
fragmentManager = fragmentManager
483498
)

course/src/main/java/org/openedx/course/presentation/container/CourseContainerViewModel.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import org.openedx.core.system.connection.NetworkConnection
3131
import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent
3232
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
3333
import org.openedx.core.system.notifier.CourseCompletionSet
34-
import org.openedx.core.system.notifier.CourseDataReady
3534
import org.openedx.core.system.notifier.CourseDatesShifted
3635
import org.openedx.core.system.notifier.CourseLoading
3736
import org.openedx.core.system.notifier.CourseNotifier
@@ -169,12 +168,7 @@ class CourseContainerViewModel(
169168
_showProgress.value = true
170169
viewModelScope.launch {
171170
try {
172-
if (networkConnection.isOnline()) {
173-
interactor.preloadCourseStructure(courseId)
174-
} else {
175-
interactor.preloadCourseStructureFromCache(courseId)
176-
}
177-
val courseStructure = interactor.getCourseStructureFromCache()
171+
val courseStructure = interactor.getCourseStructure(courseId)
178172
courseName = courseStructure.name
179173
_organization = courseStructure.org
180174
_isSelfPaced = courseStructure.isSelfPaced
@@ -183,7 +177,6 @@ class CourseContainerViewModel(
183177
val isReady = start < Date()
184178
if (isReady) {
185179
_isNavigationEnabled.value = true
186-
courseNotifier.send(CourseDataReady(courseStructure))
187180
}
188181
isReady
189182
}
@@ -248,7 +241,7 @@ class CourseContainerViewModel(
248241
fun updateData() {
249242
viewModelScope.launch {
250243
try {
251-
interactor.preloadCourseStructure(courseId)
244+
interactor.getCourseStructure(courseId, isNeedRefresh = true)
252245
} catch (e: Exception) {
253246
if (e.isInternetError()) {
254247
_errorMessage.value =

course/src/main/java/org/openedx/course/presentation/dates/CourseDatesViewModel.kt

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ import org.openedx.core.data.storage.CorePreferences
1919
import org.openedx.core.domain.model.Block
2020
import org.openedx.core.domain.model.CourseBannerType
2121
import org.openedx.core.domain.model.CourseDateBlock
22+
import org.openedx.core.domain.model.CourseStructure
2223
import org.openedx.core.extension.getSequentialBlocks
2324
import org.openedx.core.extension.getVerticalBlocks
2425
import org.openedx.core.extension.isInternetError
2526
import org.openedx.core.presentation.course.CourseContainerTab
2627
import org.openedx.core.system.ResourceManager
2728
import org.openedx.core.system.notifier.CalendarSyncEvent.CheckCalendarSyncEvent
2829
import org.openedx.core.system.notifier.CalendarSyncEvent.CreateCalendarSyncEvent
29-
import org.openedx.core.system.notifier.CourseDataReady
3030
import org.openedx.core.system.notifier.CourseDatesShifted
3131
import org.openedx.core.system.notifier.CourseLoading
3232
import org.openedx.core.system.notifier.CourseNotifier
@@ -41,6 +41,8 @@ import org.openedx.course.presentation.calendarsync.CalendarSyncUIState
4141
import org.openedx.core.R as CoreR
4242

4343
class CourseDatesViewModel(
44+
val courseId: String,
45+
courseTitle: String,
4446
private val enrollmentMode: String,
4547
private val courseNotifier: CourseNotifier,
4648
private val interactor: CourseInteractor,
@@ -51,8 +53,6 @@ class CourseDatesViewModel(
5153
private val config: Config,
5254
) : BaseViewModel() {
5355

54-
var courseId = ""
55-
var courseName = ""
5656
var isSelfPaced = true
5757

5858
private val _uiState = MutableLiveData<DatesUIState>(DatesUIState.Loading)
@@ -66,14 +66,15 @@ class CourseDatesViewModel(
6666
private val _calendarSyncUIState = MutableStateFlow(
6767
CalendarSyncUIState(
6868
isCalendarSyncEnabled = isCalendarSyncEnabled(),
69-
calendarTitle = calendarManager.getCourseCalendarTitle(courseName),
69+
calendarTitle = calendarManager.getCourseCalendarTitle(courseTitle),
7070
isSynced = false,
7171
)
7272
)
7373
val calendarSyncUIState: StateFlow<CalendarSyncUIState> =
7474
_calendarSyncUIState.asStateFlow()
7575

7676
private var courseBannerType: CourseBannerType = CourseBannerType.BLANK
77+
private var courseStructure: CourseStructure? = null
7778

7879
val isCourseExpandableSectionsEnabled get() = config.isCourseNestedListEnabled()
7980

@@ -90,22 +91,19 @@ class CourseDatesViewModel(
9091
loadingCourseDatesInternal()
9192
}
9293
}
93-
94-
is CourseDataReady -> {
95-
courseId = event.courseStructure.id
96-
courseName = event.courseStructure.name
97-
isSelfPaced = event.courseStructure.isSelfPaced
98-
loadingCourseDatesInternal()
99-
updateAndFetchCalendarSyncState()
100-
}
10194
}
10295
}
10396
}
97+
98+
loadingCourseDatesInternal()
99+
updateAndFetchCalendarSyncState()
104100
}
105101

106102
private fun loadingCourseDatesInternal() {
107103
viewModelScope.launch {
108104
try {
105+
courseStructure = interactor.getCourseStructure(courseId = courseId)
106+
isSelfPaced = courseStructure?.isSelfPaced ?: false
109107
val datesResponse = interactor.getCourseDates(courseId = courseId)
110108
if (datesResponse.datesSection.isEmpty()) {
111109
_uiState.value = DatesUIState.Empty
@@ -146,18 +144,17 @@ class CourseDatesViewModel(
146144

147145
fun getVerticalBlock(blockId: String): Block? {
148146
return try {
149-
val courseStructure = interactor.getCourseStructureFromCache()
150-
courseStructure.blockData.getVerticalBlocks().find { it.descendants.contains(blockId) }
147+
courseStructure?.blockData?.getVerticalBlocks()
148+
?.find { it.descendants.contains(blockId) }
151149
} catch (e: Exception) {
152150
null
153151
}
154152
}
155153

156154
fun getSequentialBlock(blockId: String): Block? {
157155
return try {
158-
val courseStructure = interactor.getCourseStructureFromCache()
159-
courseStructure.blockData.getSequentialBlocks()
160-
.find { it.descendants.contains(blockId) }
156+
courseStructure?.blockData?.getSequentialBlocks()
157+
?.find { it.descendants.contains(blockId) }
161158
} catch (e: Exception) {
162159
null
163160
}

0 commit comments

Comments
 (0)