Search code examples
mvvmkotlinsurfaceviewandroid-roomandroid-livedata

How to observe data generated from custom surface view


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...


Solution

  • 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