Android-приложение получает данные с сервера, сохраняет их в базе данных и отображает для пользователя - PullRequest
0 голосов
/ 18 октября 2019

Я переписываю приложение, которое включает получение данных с сервера через REST, сохранение их в базе данных на каждом устройстве Android, а затем отображение этих данных пользователю. Данные, извлекаемые с сервера, имеют параметр «с», поэтому он не будет возвращать все данные, только данные, которые изменились с момента последнего извлечения.

У меня извлечение данных с сервера работает нормально, ноЯ не уверен, что лучший способ сохранить эти данные в базе данных, , а затем показать их пользователю. Я использую Kotlin, Retrofit, Room и LiveData.

Приведенный ниже код является упрощенной версией того, что я на самом деле делаю, но в нем все понятно.

MyData.kt (модель)

@Entity(tableName = "MyTable")
data class MyData(
  @PrimaryKey(autoGenerate = true)
  @ColumnInfo(name = "id")
  var id Int? = null,

  @SerializedName("message")
  @ColumnInfo(name = "message")
  var message: String? = null
) {
    companion object {
      fun fromContentValues(values: ContentValues): MyData {
        val data = MyData()
        // Do this for id and message
        if (values.containsKey("id") {
          data.id = values.getAsInteger("id")
        }
      }
    }
}

DataViewModel.kt

class DataViewModel(application: Application) : AndroidViewModel(application) {
  private val repository = DataRepository()

  fun data(since: Long) =
    liveData(Dispatchers.IO) {
      val data = repository.getDataFromServer(since)
      emit(data)
    }

  fun saveData(data: List<MyData>) =
    liveData(Dispatchers.Default) {
      val result = repository.saveDataToDatabase(data)
      emit(result)
    }

  fun data() =
    liveData(Dispatchers.IO) {
      val data = repository.getDataFromDatabase()
      emit(data)
    }
}

DataRepository.kt

class DataRepository(application: Application) {

  // I won't add how the Retrofit client is created, it's standard
  private var client = "MyUrlToGetDataFrom"

  private var myDao: MyDao

  init {
    val myDatabase = MyDatabase.getDatabase(application)
    myDao = myDatabase!!.myDao()
  }

  suspend fun getDataFromServer(since: Long): List<MyData> {
    try {
        return client.getData(since)
    } catch (e: Exception) {

    }
  }

  fun getDataFromDatabase(): List<MyData> = myDao.getAll()

  suspend fun insertData(data: List<MyData>) = 
    myDao.insertData(data)
  }

MyDao.kt

@Dao
interface PostsDao {

    @Query("SELECT * FROM " + Post.TABLE_NAME + " ORDER BY " + Post.COLUMN_ID + " desc")
    suspend fun getAllData(): List<MyData>

    @Insert
    suspend fun insertData(data: List<MyData>)
}

ListActivity.kt

private lateinit var mDataViewModel: DataViewModel

override fun onCreate(savedInstanceBundle: Bundle?) {
  super.onCreate(savedInstanceBundle)
  mDataViewModel = ViewModelProvider(this, DataViewModelFactory(contentResolver)).get(DataViewModel::class.java)

  getData()
}

private fun getData() {
  mDataViewModel.data(getSince()).observe(this, Observer {
    saveData(it)
  })
}

private fun saveData(data: List<MyData>) {
  mDataViewModel.saveData(data)
  mDataViewModel.data().observe(this, Observer {
    setupRecyclerView(it)
  })
}

ListActivity.kt и, возможно, классы ViewModel и Repository, где он использует сопрограммы, находятся там, где я застрял. getData () извлекает данные с сервера без проблем, но когда дело доходит до сохранения их в базе данных, затем извлечения этих сохраненных данных из базы данных и их отображения пользователю, я не уверен в подходе. Как я уже говорил, я использую Room, но Room не позволит вам получить доступ к базе данных в главном потоке.

Помните, сначала нужно сохранить в базе данных, а затем извлечь из базы данных, поэтому я неЯ не хочу звонить mDataViewModel.data().observe до тех пор, пока он не сохранится в базе данных.

Каков правильный подход к этому? Я попытался сделать CoroutineScope для mDataViewModel.saveData (), затем .invokeOnCompletion, чтобы сделать mDataViewModel.data().observe, но он не сохраняется в базе данных. Я предполагаю, что я делаю мои сопрограммы неправильно, но не уверен, где именно.

Это также в конечном счете необходимо будет удалить и обновить записи из базы данных.

1 Ответ

1 голос
/ 23 октября 2019

Обновленный ответ

После прочтения комментариев и обновленного вопроса я понял, что вы хотите получить небольшой список данных, сохранить его в базе данных и показать все данные, хранящиеся в базе данных. Если это то, что вам нужно, вы можете выполнить следующее (для краткости опущен DataSouce) -

In PostDao Вы можете вернуть LiveData<List<MyData>> вместо List<MyData> и наблюдать, чтоLiveData в Activity для обновления RecyclerView. Просто убедитесь, что вы удалили ключевое слово suspend, так как room позаботится о потоке, когда вернет LiveData.

@Dao
interface PostsDao {
    @Query("SELECT * FROM " + Post.TABLE_NAME + " ORDER BY " + Post.COLUMN_ID + " desc")
    fun getAllData(): LiveData<List<MyData>>

    @Insert
    suspend fun insertData(data: List<MyData>)
}

В репозитории сделайте 2 функции по одной для извлечения удаленных данных и их сохранения вбаза данных, а другой просто возвращает LiveData, возвращаемый room. Вам не нужно делать запрос на room при вставке удаленных данных, room автоматически обновит вас, когда вы наблюдаете LiveData с room.

class DataRepository(private val dao: PostsDao, private val dto: PostDto) {

    fun getDataFromDatabase() = dao.getAllData()

    suspend fun getDataFromServer(since: Long) = withContext(Dispatchers.IO) {
        val data = dto.getRemoteData(since)
        saveDataToDatabase(data)
    }

    private suspend fun saveDataToDatabase(data: List<MyData>) = dao.insertData(data)
}

ВашViewModel должен выглядеть следующим образом:

class DataViewModel(private val repository : DataRepository) : ViewModel() {

    val dataList = repository.getDataFromDatabase()

    fun data(since: Long) = viewModelScope.launch {
       repository.getDataFromServer(since)
    }
}

В упражнении убедитесь, что вы используете ListAdapter

private lateinit var mDataViewModel: DataViewModel
private lateinit var mAdapter: ListAdapter

override fun onCreate(savedInstanceBundle: Bundle?) {
    ...
    mDataViewModel.data(getSince())
    mDataViewModel.dataList.observe(this, Observer(adapter::submitList))
}

Начальный ответ

Прежде всего, ярекомендую взглянуть на Android Architecture Blueprints v2 . В соответствии с Android Architecture Blueprints v2 могут быть сделаны следующие улучшения,

  1. DataRepository должно быть введено , а не реализовано внутренне в соответствии с принципом инверсии зависимости.
  2. Вы должны отделить функции в ViewModel. Вместо возврата LiveData функция data() может обновить инкапсулированный LiveData. Например,

    class DataViewModel(private val repository = DataRepository) : ViewModel() {
        private val _dataList = MutableLiveData<List<MyData>>()
        val dataList : LiveData<List<MyData>> = _dataList
    
        fun data(since: Long) = viewModelScope.launch {
            val list = repository.getData(since)
            _dataList.value = list
        }
        ...
    }
    
  3. Repository должен отвечать за выборку данных из удаленного источника данных и сохранение их в локальном источнике данных. У вас должно быть два источника данных, то есть RemoteDataSource и LocalDataSource, которые должны быть введены в репозиторий. Вы также можете иметь реферат DataSource. Давайте посмотрим, как вы можете улучшить свой репозиторий,

    interface DataSource {
        suspend fun getData(since: Long) : List<MyData>
        suspend fun saveData(list List<MyData>)
        suspend fun delete()
    }
    
    class RemoteDataSource(dto: PostsDto) : DataSource { ... }
    class LocalDataSource(dao: PostsDao) : DataSource { ... }
    
    class DataRepository(private val remoteSource: DataSource, private val localSource: DataSource) {
        suspend fun getData(since: Long) : List<MyData> = withContext(Dispatchers.IO) {
            val data = remoteSource.getData(since)
            localSource.delete()
            localSource.save(data)
            return@withContext localSource.getData(since)
        }
        ...
    }
    
  4. В вашем Activity вам просто нужно соблюдать dataList: LiveData и передать его значение ListAdapter.

    private lateinit var mDataViewModel: DataViewModel
    private lateinit var mAdapter: ListAdapter
    
    override fun onCreate(savedInstanceBundle: Bundle?) {
        ...
        mDataViewModel.data(since)
        mDataViewModel.dataList.observe(this, Observer(adapter::submitList))
    }
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...