I'm setting up room database in my application and I want to get data from user, who is drawing on custom surface view.
App is running on Android 9, using MVVM pattern, I tried app without Room and it was working properly. After implementing Room, app don't observe data changes anymore. I used google codelabs as template for my app. User is drawing nodes of graph on surface view by clicking on view and I want to store data of place(node) in database.
Place:
@Entity(tableName = "places")
data class Place(
@Embedded
var mPosition: Point,
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "place_id")
var mID: Int = -1,
@ColumnInfo(name = "name")
override var mName: String = "",
@ColumnInfo(name = "tokens")
var mTokens: Int = 0
) : Node() {
override fun isValid(): Boolean {
return mID == -1 && mPosition.x != 0f && mPosition.y != 0f && mTokens >= 0
}
}
PlaceDao:
@Dao
interface PlaceDao {
@Insert
fun addPlace(place: Place)
@Query("SELECT * FROM places ORDER BY place_id ASC")
fun getPlaces(): LiveData<List<Place>>
...
}
Database:
@Database(entities = [Place::class, Transition::class, Arc::class], version = 1)
@TypeConverters(Converter::class)
abstract class AppRoomDatabase : RoomDatabase() {
abstract fun placeDao(): PlaceDao
companion object {
@Volatile
private var INSTANCE: AppRoomDatabase? = null
fun getDatabase(
context: Context,
scope: CoroutineScope
): AppRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppRoomDatabase::class.java,
"word_database"
)
.fallbackToDestructiveMigration()
.addCallback(AppDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
private class AppDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch(Dispatchers.IO) {
populateDatabase(database.placeDao(),database.transitionDao(),database.arcDao())
}
}
}
}
}
}
Repository:
class NetRepositoryImpl(
private val placeDao: PlaceDao,
private val transitionDao: TransitionDao,
private val arcDao: ArcDao
) : NetRepository {
@WorkerThread
override suspend fun addPlace(place: Place) {
placeDao.addPlace(place)
}
override fun getPlaces() = placeDao.getPlaces()
}
ViewModel:
class NetViewModel(application: Application) : AndroidViewModel(application) {
private var parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Main
private val scope = CoroutineScope(coroutineContext)
private val netRepository: NetRepositoryImpl
val allPlaces: LiveData<List<Place>>
...
init {
val placeDao = AppRoomDatabase.getDatabase(application, scope).placeDao()
...
netRepository = NetRepositoryImpl(placeDao, transitionDao, arcDao)
allPlaces = netRepository.getPlaces()
...
}
fun addPlace(place: Place) {
if (place.isValid())
scope.launch(Dispatchers.IO) { netRepository.addPlace(place) }
}
Activity:
class EditorActivity : FragmentActivity() {
private lateinit var viewModel: NetViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProviders.of(this).get(NetViewModel::class.java)
viewModel.apply {
allPlaces.observe(this@EditorActivity, Observer { places ->
editorDrawer.mPlaces = places
Log.d("Editor activity", "Places observed")
})
...
editorDrawer.setOnTouchListener { v, event ->
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
...
viewModel.addPlace(Point(event.x, event.y))
...
}
}
...
}
editorDrawer is custom surface view in activity layout.
When user clicks on surface view I expect to create new Place and store it in database, then draw it on surface view when data is changed, new instance of place is created but not stored and no changes are observed so no place is drawn. I can find in console log
"Places observed"
only on app startup, like if data don't change at all...
Well problem wasn't related to Room or MVVM pattern, it was byproduct of my carelessness... Error was in if condition in isValid() function of place entity class... Just changed
return mID == -1 && mPosition.x != 0f && mPosition.y != 0f && mTokens >= 0
to
return mID != -1 && mPosition.x != 0f && mPosition.y != 0f && mTokens >= 0