Android 应用程序从服务器检索数据,保存在数据库中并显示给用户
Android app retrieve data from server, save in database and display to user
我正在重写一个应用程序,该应用程序涉及通过 REST 从服务器检索数据,将其保存到每个 Android 设备上的数据库,然后将该数据显示给用户。从服务器检索的数据有一个 "since" 参数,因此它不会 return 所有数据,只是自上次检索以来发生变化的数据。
我从服务器检索工作正常,但我不确定将数据保存到数据库的最佳方法,然后 显示给用户。我正在使用 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
。
正确的做法是什么?我尝试在 mDataViewModel.saveData() 上执行 CoroutineScope,然后在 .invokeOnCompletion
上执行 mDataViewModel.data().observe
,但它不会保存到数据库中。我猜我的协程做错了,但不确定具体在哪里。
它最终还需要从数据库中删除和更新记录。
更新答案
阅读评论和更新问题后,我发现您想获取一小部分数据并将其存储到数据库并显示存储在数据库中的所有数据。如果这是你想要的,你可以执行以下 (为简洁起见省略了 DataSouce) -
在 PostDao
你可以 return 一个 LiveData<List<MyData>>
而不是 List<MyData>
并观察 Activity 中的 LiveData
来更新 RecyclerView
。只需确保删除 suspend
关键字,因为 room
将在 returns 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 个函数,一个用于获取远程数据并将其存储到数据库中,另一个只是 returns LiveData
return 由 room
编辑.当您插入远程数据时,您不需要向 room
发出请求,room
会在您观察来自 room
的 LiveData
时自动更新您。
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. According to Android Architecture Blueprints v2 可以进行以下改进,
DataRepository
应该注入而不是根据依赖倒置原则在内部实例化
您应该解耦 ViewModel
中的函数。代替 returning 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
}
...
}
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)
}
...
}
在您的 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))
}
我正在重写一个应用程序,该应用程序涉及通过 REST 从服务器检索数据,将其保存到每个 Android 设备上的数据库,然后将该数据显示给用户。从服务器检索的数据有一个 "since" 参数,因此它不会 return 所有数据,只是自上次检索以来发生变化的数据。
我从服务器检索工作正常,但我不确定将数据保存到数据库的最佳方法,然后 显示给用户。我正在使用 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
。
正确的做法是什么?我尝试在 mDataViewModel.saveData() 上执行 CoroutineScope,然后在 .invokeOnCompletion
上执行 mDataViewModel.data().observe
,但它不会保存到数据库中。我猜我的协程做错了,但不确定具体在哪里。
它最终还需要从数据库中删除和更新记录。
更新答案
阅读评论和更新问题后,我发现您想获取一小部分数据并将其存储到数据库并显示存储在数据库中的所有数据。如果这是你想要的,你可以执行以下 (为简洁起见省略了 DataSouce) -
在 PostDao
你可以 return 一个 LiveData<List<MyData>>
而不是 List<MyData>
并观察 Activity 中的 LiveData
来更新 RecyclerView
。只需确保删除 suspend
关键字,因为 room
将在 returns 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 个函数,一个用于获取远程数据并将其存储到数据库中,另一个只是 returns LiveData
return 由 room
编辑.当您插入远程数据时,您不需要向 room
发出请求,room
会在您观察来自 room
的 LiveData
时自动更新您。
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. According to Android Architecture Blueprints v2 可以进行以下改进,
DataRepository
应该注入而不是根据依赖倒置原则在内部实例化您应该解耦
ViewModel
中的函数。代替 returningLiveData
,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 } ... }
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) } ... }
在您的
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)) }