카테고리 없음
[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를 수행하도록 한다.