繁体   English   中英

Android 应用程序从服务器检索数据,保存在数据库中并显示给用户

[英]Android app retrieve data from server, save in database and display to user

我正在重写一个应用程序,该应用程序涉及通过 REST 从服务器检索数据,将其保存到每个 Android 设备上的数据库中,然后将该数据显示给用户。 从服务器检索的数据有一个“since”参数,所以它不会返回所有数据,只返回自上次检索以来发生变化的数据。

我从服务器检索工作正常,但我不确定将数据保存到数据库然后将其显示给用户的最佳方法。 我正在使用 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")
        }
      }
    }
}

数据视图模型.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)
    }
}

数据存储库.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)
  }

我的道.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>)
}

列表活动.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

对此的正确方法是什么? 我尝试在 mDataViewModel.saveData() 上执行 CoroutineScope,然后在.invokeOnCompletion上执行mDataViewModel.data().observe ,但它不会保存到数据库中。 我猜我的协程做错了,但不确定到底在哪里。

它最终还需要从数据库中删除和更新记录。

更新的答案

在阅读评论和更新问题后,我发现您想要获取一小部分数据并将其存储到数据库并显示存储在数据库中的所有数据。 如果这是您想要的,您可以执行以下操作(为简洁起见,省略了 DataSouce) -

PostDao您可以返回LiveData<List<MyData>>而不是List<MyData>并观察 Activity 中的LiveData以更新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>)
}

在 Repository 中创建 2 个函数,一个用于获取远程数据并将其存储到数据库中,另一个仅返回room返回的LiveData 当您插入远程数据时,您不需要向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)
    }
}

在 Activity 中确保使用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中的功能。 data() function 可以更新封装的LiveData ,而不是返回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应该负责从远程数据源获取数据并保存到本地数据源。 您应该有两个数据源,即RemoteDataSourceLocalDataSource ,它们应该被注入到存储库中。 你也可以有一个抽象的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)) }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM