카테고리 없음

[Android] Room with Observable Query

Jun.LEE 2024. 3. 27. 21:15

Room DB에 관찰가능한 쿼리를 작성해 보려고 한다.

관찰가능한 쿼리란 참조하는 테이블에 변경이 생기면 새 값을 읽을 수 있도록 하는 작업이다.

 

먼저 Data Layer를 구성해주자.

@Database(entities = [Node::class], version = 1)
abstract class AppDatabase: RoomDatabase() {

    abstract fun nodeDao(): NodeDao
    
    companion object {
        private var INSTANCE: AppDatabase? = null
        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    "app_db"
                ).build()
            }
        }
    }
}
@Dao
interface NodeDao {
    @Query("INSERT INTO Node (x, y) VALUES (:x, :y)")
    suspend fun insertNode(
        x: Double,
        y: Double
    ): Long
    @Query("SELECT * FROM Node")
    fun getAllNodes(): LiveData<List<Node>>
}
@Entity(tableName = "Node")
data class Node(
    @PrimaryKey(autoGenerate = true)
    val id: Long = -1,
    @ColumnInfo(defaultValue = "0.0")
    val x: Double = 0.0,
    @ColumnInfo(defaultValue = "0.0")
    val y: Double = 0.0
)
class NodeLocalSource(private val nodeDao: NodeDao) {
    fun getAllNodes() = nodeDao.getAllNodes()
    suspend fun insertNodes(nodes: List<Node>) = nodeDao.insertNodes(nodes)
    suspend fun insertNode(
        x: Double,
        y: Double
    ): Long = nodeDao.insertNode(x, y)
}
class NodeRepository(private val nodeSource: NodeLocalSource) {
    val nodes = nodeSource.getAllNodes()
    suspend fun insertNodes(nodes: List<Node>) = nodeSource.insertNodes(nodes)
    suspend fun insertNode(
        x: Double,
        y: Double
    ): Long = nodeSource.insertNode(x, y)
}

 

마지막으로 UI Layer를 구성해주자.

class GraphViewModel(
    application: Application
): ViewModel() {
    private val db: AppDatabase = AppDatabase.getInstance(application)
    private val nodeRepository = NodeRepository(NodeLocalSource(db.nodeDao()))
    val nodes = nodeRepository.nodes
    val buttonClickListener: () -> Unit = {
        thread(start = true) {
            viewModelScope.launch(Dispatchers.IO) {
                nodeRepository.insertNode(
                    x = 0.0,
                    y = 0.0,
                )
            }
        }
    }

}
class MainActivity : ComponentActivity() {

    private lateinit var viewModel: GraphViewModel

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = GraphViewModel(application)
        setContent {
            NetWorkMemoTheme {
                val nodes = viewModel.nodes.observeAsState(initial = listOf())
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Scaffold(
                        floatingActionButton = {
                            FloatingActionButton(onClick = viewModel.buttonClickListener) {
                                Icon(imageVector = Icons.Filled.Add, contentDescription = "")
                            }
                        }
                    ) {
                        LazyColumn(
                            modifier = Modifier
                                .fillMaxSize()
                                .padding(it)
                        ) {
                            items(nodes.value) { node ->
                                Box(
                                    modifier = Modifier
                                        .fillMaxWidth()
                                        .height(72.dp)
                                        .padding(12.dp)
                                        .background(color = Color.Blue)
                                )
                            }
                        }
                    }
                }
            }
        }
    }
}

 

위와 같이 구성하면 Node Insert를 수행한 후에 별도의 작업 없이 데이터를 읽어올 수 있다.

 

 

 

어플리케이션을 빌드하면 RoomDatabase는 Dao annotation이 있는 interface의 implementation을 구성해준다.

이를 확인해 보면 다음과 같다.

@Override
public LiveData<List<Node>> getAllNodes() {
  final String _sql = "SELECT * FROM Node";
  final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
  return __db.getInvalidationTracker().createLiveData(new String[]{"Node"}, false, new Callable<List<Node>>() {
    @Override
    public List<Node> call() throws Exception {
      final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
      try {
        /** Table Column 읽어서 _item에 넣어주는 작업 ~ */
          _result.add(_item);
        }
        return _result;
      } finally {
        _cursor.close();
      }
    }

    @Override
    protected void finalize() {
      _statement.release();
    }
  });
}

 

RoomDatabase에 InvalidationTracker를 통해 LiveData를 관리한다.

그리고 InvalidationTracker는 Table에 INSERT, DELETE, UPDATE의 쿼리가 생기면

트리거를 작동시킴으로서 LiveData가 onChanged를 수행하도록 한다.