From 5b1bf943e9068cd4443653f96d596284db9bbd40 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Sat, 13 Mar 2021 18:16:54 +0000 Subject: [PATCH] Now using repository external scope for sat data update --- .../java/com/rtbishop/look4sat/data/Result.kt | 2 +- .../rtbishop/look4sat/di/ApplicationModule.kt | 4 - .../rtbishop/look4sat/di/CoroutinesModule.kt | 13 +- .../com/rtbishop/look4sat/di/NetworkModule.kt | 3 - .../rtbishop/look4sat/di/RepositoryModule.kt | 5 +- .../rtbishop/look4sat/repository/PrefsRepo.kt | 2 + .../look4sat/repository/SatelliteRepo.kt | 126 +++++++++++------- .../repository/localData/SatelliteDao.kt | 20 +-- .../rtbishop/look4sat/ui/SharedViewModel.kt | 57 +++----- .../ui/entriesScreen/EntriesFragment.kt | 14 +- 10 files changed, 125 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/com/rtbishop/look4sat/data/Result.kt b/app/src/main/java/com/rtbishop/look4sat/data/Result.kt index 8923e890..8bd02d00 100644 --- a/app/src/main/java/com/rtbishop/look4sat/data/Result.kt +++ b/app/src/main/java/com/rtbishop/look4sat/data/Result.kt @@ -19,6 +19,6 @@ package com.rtbishop.look4sat.data sealed class Result { data class Success(val data: T) : Result() - data class Error(val exception: Exception) : Result() + data class Error(val exception: Throwable) : Result() object InProgress : Result() } \ No newline at end of file diff --git a/app/src/main/java/com/rtbishop/look4sat/di/ApplicationModule.kt b/app/src/main/java/com/rtbishop/look4sat/di/ApplicationModule.kt index c4817271..e09206e6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/ApplicationModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/di/ApplicationModule.kt @@ -27,26 +27,22 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object ApplicationModule { @Provides - @Singleton fun provideContentResolver(@ApplicationContext context: Context): ContentResolver { return context.contentResolver } @Provides - @Singleton fun provideLocationManager(@ApplicationContext context: Context): LocationManager { return context.getSystemService(Context.LOCATION_SERVICE) as LocationManager } @Provides - @Singleton fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } diff --git a/app/src/main/java/com/rtbishop/look4sat/di/CoroutinesModule.kt b/app/src/main/java/com/rtbishop/look4sat/di/CoroutinesModule.kt index 95f857c5..926c6b4b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/CoroutinesModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/di/CoroutinesModule.kt @@ -26,29 +26,32 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import javax.inject.Qualifier -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object CoroutinesModule { @Provides - @Singleton - fun provideApplicationScope(): CoroutineScope = CoroutineScope(SupervisorJob()) + @ExternalScope + fun provideExternalScope(): CoroutineScope = CoroutineScope(SupervisorJob()) @Provides @DefaultDispatcher fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default - + @Provides @IoDispatcher fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO - + @Provides @MainDispatcher fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main } +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class ExternalScope + @Qualifier @Retention(AnnotationRetention.BINARY) annotation class DefaultDispatcher diff --git a/app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt b/app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt index 6486fbb7..e2acf5bb 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt @@ -25,20 +25,17 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides - @Singleton fun provideMoshi(): Moshi { return Moshi.Builder().build() } @Provides - @Singleton fun provideSatDataService(): SatelliteService { return Retrofit.Builder() .baseUrl("https://db.satnogs.org/api/") diff --git a/app/src/main/java/com/rtbishop/look4sat/di/RepositoryModule.kt b/app/src/main/java/com/rtbishop/look4sat/di/RepositoryModule.kt index 3d033f7a..4296ca32 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/RepositoryModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/di/RepositoryModule.kt @@ -20,29 +20,26 @@ package com.rtbishop.look4sat.di import android.content.Context import androidx.room.Room import com.rtbishop.look4sat.repository.localData.MIGRATION_1_2 +import com.rtbishop.look4sat.repository.localData.RoomConverters import com.rtbishop.look4sat.repository.localData.SatelliteDao import com.rtbishop.look4sat.repository.localData.SatelliteDb -import com.rtbishop.look4sat.repository.localData.RoomConverters import com.squareup.moshi.Moshi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object RepositoryModule { @Provides - @Singleton fun provideSatDataDao(db: SatelliteDb): SatelliteDao { return db.satelliteDao() } @Provides - @Singleton fun provideSatelliteDb(@ApplicationContext context: Context, moshi: Moshi): SatelliteDb { RoomConverters.initialize(moshi) return Room.databaseBuilder(context, SatelliteDb::class.java, "satDb") diff --git a/app/src/main/java/com/rtbishop/look4sat/repository/PrefsRepo.kt b/app/src/main/java/com/rtbishop/look4sat/repository/PrefsRepo.kt index f794b9de..d8b89c74 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repository/PrefsRepo.kt +++ b/app/src/main/java/com/rtbishop/look4sat/repository/PrefsRepo.kt @@ -25,7 +25,9 @@ import com.rtbishop.look4sat.data.TleSource import com.squareup.moshi.Moshi import com.squareup.moshi.Types import javax.inject.Inject +import javax.inject.Singleton +@Singleton class PrefsRepo @Inject constructor(val preferences: SharedPreferences, moshi: Moshi) { private val sourcesType = Types.newParameterizedType(List::class.java, TleSource::class.java) diff --git a/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt b/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt index 10ec4731..9a598449 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt +++ b/app/src/main/java/com/rtbishop/look4sat/repository/SatelliteRepo.kt @@ -21,70 +21,102 @@ import android.content.ContentResolver import android.net.Uri import com.github.amsacode.predict4java.Satellite import com.github.amsacode.predict4java.TLE -import com.rtbishop.look4sat.data.SatEntry -import com.rtbishop.look4sat.data.SatItem -import com.rtbishop.look4sat.data.SatTrans -import com.rtbishop.look4sat.data.TleSource +import com.rtbishop.look4sat.data.* +import com.rtbishop.look4sat.di.ExternalScope import com.rtbishop.look4sat.di.IoDispatcher import com.rtbishop.look4sat.repository.localData.SatelliteDao import com.rtbishop.look4sat.repository.remoteData.SatelliteService import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import java.io.InputStream import java.util.zip.ZipInputStream import javax.inject.Inject +import javax.inject.Singleton +import kotlin.system.measureTimeMillis +@Singleton class SatelliteRepo @Inject constructor( private val resolver: ContentResolver, private val satelliteDao: SatelliteDao, private val satelliteService: SatelliteService, + @ExternalScope private val externalScope: CoroutineScope, @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) { - - fun getSatItems(): Flow> { - return satelliteDao.getSatItems() + private val _satData = MutableStateFlow>>(Result.InProgress) + val satData: Flow>> = _satData + + init { + loadEntriesFromDb() } - - suspend fun getSelectedSatellites(): List { - return satelliteDao.getSelectedSatellites() - } - - suspend fun updateEntriesSelection(catNums: List) { - satelliteDao.updateEntriesSelection(catNums) - } - - suspend fun updateEntriesFromFile(uri: Uri) { - withContext(ioDispatcher) { - runCatching { - resolver.openInputStream(uri)?.use { stream -> - updateAndRestoreSelection(importEntriesFromStreams(listOf(stream))) - } - } - } - } - - suspend fun updateEntriesFromWeb(sources: List) { - withContext(ioDispatcher) { - val streams = getStreamsForSources(sources) - val entries = importEntriesFromStreams(streams) - updateAndRestoreSelection(entries) - } - } - + fun getTransmittersForSat(catNum: Int): Flow> { return satelliteDao.getTransmittersForSat(catNum) } - - suspend fun updateTransmitters() { - satelliteDao.insertTransmitters(satelliteService.fetchTransmitters()) + + suspend fun getSelectedSatellites(): List { + return satelliteDao.getSelectedSatellites() } - + + suspend fun updateEntriesSelection(catNums: List) { + satelliteDao.updateEntriesSelection(catNums) + } + + suspend fun updateSatDataFromFile(uri: Uri) { + _satData.value = Result.InProgress + externalScope.launch { + runCatching { + resolver.openInputStream(uri)?.use { stream -> + val entries = importEntriesFromStreams(listOf(stream)) + insertEntriesAndRestoreSelection(entries) + loadEntriesFromDb() + } + }.onFailure { throwable: Throwable -> + Timber.d("$throwable") + _satData.value = Result.Error(throwable) + } + } + } + + suspend fun updateSatDataFromWeb(sources: List) { + _satData.value = Result.InProgress + externalScope.launch { + val updateTimeMillis = measureTimeMillis { + runCatching { + val transmitters = satelliteService.fetchTransmitters() + val streams = getStreamsForSources(sources) + val entries = importEntriesFromStreams(streams) + satelliteDao.insertTransmitters(transmitters) + insertEntriesAndRestoreSelection(entries) + loadEntriesFromDb() + }.onFailure { throwable: Throwable -> + Timber.d("$throwable") + _satData.value = Result.Error(throwable) + } + } + Timber.d("Update from Web took $updateTimeMillis ms") + } + } + + private fun loadEntriesFromDb() { + _satData.value = Result.InProgress + externalScope.launch { + satelliteDao.getSatItems().collect { satItems -> + _satData.value = Result.Success(satItems) + } + } + } + private suspend fun getStreamsForSources(sources: List): List { val streams = mutableListOf() sources.forEach { tleSource -> satelliteService.fetchFile(tleSource.url).body()?.byteStream()?.let { inputStream -> - if (tleSource.url.contains(".zip")) { + if (tleSource.url.contains(".zip", true)) { // Handle zip stream val zipInputStream = ZipInputStream(inputStream) val zipEntry = zipInputStream.nextEntry @@ -96,17 +128,19 @@ class SatelliteRepo @Inject constructor( } return streams } - - private fun importEntriesFromStreams(streams: List): List { + + private suspend fun importEntriesFromStreams(streams: List): List { val importedEntries = mutableListOf() - streams.forEach { stream -> - val entries = TLE.importSat(stream).map { tle -> SatEntry(tle) } - importedEntries.addAll(entries) + withContext(ioDispatcher) { + streams.forEach { stream -> + val entries = TLE.importSat(stream).map { tle -> SatEntry(tle) } + importedEntries.addAll(entries) + } } return importedEntries } - - private suspend fun updateAndRestoreSelection(entries: List) { + + private suspend fun insertEntriesAndRestoreSelection(entries: List) { val selectedCatNums = satelliteDao.getSelectedCatNums() satelliteDao.insertEntries(entries) satelliteDao.updateEntriesSelection(selectedCatNums) diff --git a/app/src/main/java/com/rtbishop/look4sat/repository/localData/SatelliteDao.kt b/app/src/main/java/com/rtbishop/look4sat/repository/localData/SatelliteDao.kt index db888e03..4616627c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repository/localData/SatelliteDao.kt +++ b/app/src/main/java/com/rtbishop/look4sat/repository/localData/SatelliteDao.kt @@ -26,37 +26,37 @@ import kotlinx.coroutines.flow.Flow @Dao interface SatelliteDao { - + @Query("SELECT * FROM entries ORDER BY name ASC") fun getEntries(): Flow> - + @Query("SELECT catNum, name, isSelected FROM entries ORDER BY name ASC") fun getSatItems(): Flow> - + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertEntries(entries: List) - + @Query("SELECT tle FROM entries WHERE isSelected = 1") suspend fun getSelectedSatellites(): List - + @Query("SELECT catNum FROM entries WHERE isSelected = 1") suspend fun getSelectedCatNums(): List - + @Transaction suspend fun updateEntriesSelection(catNums: List) { clearSelection() catNums.forEach { catNum -> updateSelection(catNum) } } - + @Query("UPDATE entries SET isSelected = 0") suspend fun clearSelection() - + @Query("UPDATE entries SET isSelected = 1 WHERE catNum = :catNum") suspend fun updateSelection(catNum: Int) - + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTransmitters(transmitters: List) - + @Query("SELECT * FROM transmitters WHERE isAlive = 1 and catNum = :catNum") fun getTransmittersForSat(catNum: Int): Flow> } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt index ec0cb20a..86904f8e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt @@ -31,7 +31,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import java.util.* import javax.inject.Inject @@ -41,32 +40,30 @@ class SharedViewModel @Inject constructor( private val prefsRepo: PrefsRepo, private val satelliteRepo: SatelliteRepo, ) : ViewModel() { - + private val _passes = MutableStateFlow>>(Result.InProgress) val passes: LiveData>> = _passes.asLiveData() - - private val _satData = MutableStateFlow>>(Result.InProgress) - val satData: LiveData>> = _satData.asLiveData() - + + val satData = satelliteRepo.satData.asLiveData() + init { if (prefsRepo.isFirstLaunch()) { updateDefaultSourcesAndEntries() prefsRepo.setFirstLaunchDone() } - loadDataFromDb() calculatePasses() } - + fun getAppTimer() = liveData { while (true) { emit(System.currentTimeMillis()) delay(1000) } } - + fun getSources() = prefsRepo.loadTleSources() fun getTransmittersForSat(satId: Int) = satelliteRepo.getTransmittersForSat(satId).asLiveData() - + fun calculatePasses(dateNow: Date = Date(System.currentTimeMillis())) { _passes.value = Result.InProgress viewModelScope.launch(Dispatchers.Default) { @@ -78,31 +75,20 @@ class SharedViewModel @Inject constructor( _passes.value = Result.Success(filteredPasses) } } - + fun updateEntriesFromFile(uri: Uri) { - _satData.value = Result.InProgress viewModelScope.launch { - try { - satelliteRepo.updateEntriesFromFile(uri) - } catch (exception: Exception) { - _satData.value = Result.Error(exception) - } + satelliteRepo.updateSatDataFromFile(uri) } } - + fun updateEntriesFromSources(sources: List = prefsRepo.loadTleSources()) { - _satData.value = Result.InProgress viewModelScope.launch { - try { - prefsRepo.saveTleSources(sources) - satelliteRepo.updateEntriesFromWeb(sources) - satelliteRepo.updateTransmitters() - } catch (exception: Exception) { - _satData.value = Result.Error(exception) - } + prefsRepo.saveTleSources(sources) + satelliteRepo.updateSatDataFromWeb(sources) } } - + fun updateItemsSelection(items: List) { _passes.value = Result.InProgress viewModelScope.launch { @@ -111,14 +97,14 @@ class SharedViewModel @Inject constructor( calculatePasses() } } - + private fun getPasses(satellite: Satellite, dateNow: Date): MutableList { val predictor = satellite.getPredictor(prefsRepo.getStationPosition()) val passes = predictor.getPasses(dateNow, prefsRepo.getHoursAhead(), true) val passList = passes.map { SatPass(satellite.tle, predictor, it) } return passList as MutableList } - + private fun sortList(passes: MutableList, dateNow: Date): MutableList { val hoursAhead = prefsRepo.getHoursAhead() val dateFuture = Calendar.getInstance().apply { @@ -131,18 +117,7 @@ class SharedViewModel @Inject constructor( passes.sortBy { it.pass.startTime } return passes } - - private fun loadDataFromDb() { - _satData.value = Result.InProgress - viewModelScope.launch { - satelliteRepo.getSatItems().collect { satItems: List -> - if (satItems.isNotEmpty()) { - _satData.value = Result.Success(satItems) - } - } - } - } - + private fun updateDefaultSourcesAndEntries() { updateEntriesFromSources() } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt index ccd5cff9..b043b289 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/entriesScreen/EntriesFragment.kt @@ -36,7 +36,7 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class EntriesFragment : Fragment(R.layout.fragment_entries) { - + private val viewModel: SharedViewModel by activityViewModels() private val filePicker = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> @@ -44,14 +44,14 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { } private var binding: FragmentEntriesBinding? = null private var entriesAdapter: EntriesAdapter? = null - + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentEntriesBinding.bind(view) setupComponents() setupObservers() } - + private fun setupComponents() { entriesAdapter = EntriesAdapter() binding?.apply { @@ -69,7 +69,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { searchBar.clearFocus() } } - + private fun setupObservers() { viewModel.satData.observe(viewLifecycleOwner, { result -> when (result) { @@ -110,7 +110,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { entriesProgress.visibility = View.VISIBLE } } - + private fun setError() { binding?.apply { entriesProgress.visibility = View.INVISIBLE @@ -118,11 +118,11 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { entriesError.visibility = View.VISIBLE } } - + private fun showImportFromWebDialog() { findNavController().navigate(R.id.nav_dialog_sources) } - + private fun navigateToPasses() { binding?.searchBar?.clearFocus() entriesAdapter?.let {