Elements of Android Room
by Mark L. Murphy
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Elements of Android Room
by Mark L. Murphy
Copyright © 2019-2021 CommonsWare, LLC. All Rights Reserved.
Printed in the United States of America.
Printing History:
December 2021: FINAL Version
The CommonsWare name and logo, “Busy Coder's Guide”, and related trade dress are trademarks of CommonsWare,
LLC.
All other trademarks referenced in this book are trademarks of their respective 5rms.
The publisher and author(s) assume no responsibility for errors or omissions or for damages resulting from the use of the
information contained herein.
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Table of Contents
 $)".!*-(// $) bold-italic #1 #)" .$) /# './1 -.$*)?
I - !
# **&D.- - ,0$.$/ . ??????????????????????????????????????????????????????????????????????? 1
*0- * )/.$ ). ???????????????????????????????????????????????????????????????? 1$
&)*2' "( )/. ???????????????????????????????????????????????????????????????????????????????? 1$
I **(.$.
- )#$)" '/$*).)/*% /. ??????????????????????????????????????????????????????? P
**( ,0$- ( )/. ?????????????????????????????????????????????????????????????????????????????? Q
**(0-)$.#$)". ?????????????????????????????????????????????????????????????????????????????????? R
 /**( ???????????????????????????????????????????????????????????????????????????????????????????? W
./$)"**( ???????????????????????????????????????????????????????????????????????????????????????? W
I # **!)/$/$ .
*)8"0-$)")/$/$ . ????????????????????????????????????????????????????????????????????????????? PR
.)0 -$ . ??????????????????????????????????????????????????????????????????????????????? QX
4)($0 -$ . ????????????????????????????????????????????????????????????????????????????????? RV
/# -+ -/$*). ??????????????????????????????????????????????????????????????????????? RX
-)./$*).)**( ??????????????????????????????????????????????????????????????????????? SQ
I **()0./*(4+ .
4+ *)1 -/ -. ??????????????????????????????????????????????????????????????????????????????????? ST
(  4+ . ?????????????????????????????????????????????????????????????????????????????????? TP
I **() /$1 -( 2*-&.
**()/# $)++'$/$*)#-  ??????????????????????????????????????????? TT
**()$1 / ????????????????????????????????????????????????????????????????????????????? TV
**()*-*0/$) . ?????????????????????????????????????????????????????????????????????????? TX
**()31 ????????????????????????????????????????????????????????????????????????????????? UR
. -1' 0 -$ . ????????????????????????????????????????????????????????????????????????????? UT
**()$./ )' 0/0- ???????????????????????????????????????????????????????????????? UT
# - 4)#-*)*0.**($.! ????????????????????????????????????????????????????? UU
 $)"1$' ????????????????????????????????????????????????????????????????????????????????????????????? UU
I  '/$*).$)**(
# '..$++-*# ???????????????????????????????????????????????????????????????? UV
$./*-4*!#- $)"$./& . ??????????????????????????????????????????????????????? UW
# **(++-*# ?????????????????????????????????????????????????????????????????????????? UX
) A/*A)4 '/$*). ?????????????????????????????????????????????????????????????????????? UX
)4A/*A)4 '/$*). ????????????????????????????????????????????????????????????????????? VT
**()/$/$ ... ??????????????????????????????????????????????????????????????????????? VX
i
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I # 0++*-//. 
E)D/*0 #/#$.$. CF ??????????????????????????????????????????????? WP
# )$'' . #$.C ???????????????????????????????????????????????????????????????????? WQ
*)8"0-$)"**(D./.  .. ????????????????????????????????????????????????? WR
I /. $"-/$*).
#/D.$"-/$*)C ???????????????????????????????????????????????????????????????????????????? WV
# )* $"-/ C ??????????????????????????????????????????????????????????????????????? WW
0/$-./<*-*0/3+*-/$)"# (. ???????????????????????????????????? WW
-$/$)"$"-/$*). ?????????????????????????????????????????????????????????????????????????????? XP
(+'*4$)"$"-/$*). ????????????????????????????????????????????????????????????????????????? XP
*2**(++'$ .$"-/$*). ??????????????????????????????????????????????????????????? XR
./$)"$"-/$*). ?????????????????????????????????????????????????????????????????????????????? XS
I *'4(*-+#$)/$/$ .
*'4(*-+#$.($/# +-/ ' . ??????????????????????????????????????????????? POR
*'4(*-+#$.($/#$)"' ' ?????????????????????????????????????????????????? POX
I  !0'/'0 .)-/$')/$/$ .
 !0'/'0 .<)/# /# - !0'/'0 . ????????????????????????????????? PPT
 !0'/'0 .)). -/. ????????????????????????????????????????????????????????????????? PPU
-/$')/$/$ . ???????????????????????????????????????????????????????????????????????????????????? PPV
I **()0''A 3/ -#
#/.C ??????????????????????????????????????????????????????????????????????????????????????? PQP
++'4$)"/***( ??????????????????????????????????????????????????????????????????????? PQR
0++*-/ 4)/3 ????????????????????????????????????????????????????????????????? PRQ
$"-/$)"/* ????????????????????????????????????????????????????????????????????????????????? PRQ
I **()*):$/ .*'0/$*)
*-/ ??????????????????????????????????????????????????????????????????????????????????????????????????? PRU
$' ?????????????????????????????????????????????????????????????????????????????????????????????????????? PRW
")*- ????????????????????????????????????????????????????????????????????????????????????????????????? PRW
 +' ??????????????????????????????????????????????????????????????????????????????????????????????? PRX
*''& ?????????????????????????????????????????????????????????????????????????????????????????????? PSO
#/#*0'*0. 2$/#**(C ?????????????????????????????????????????????????? PSO
I **($/#$ 2
 8)$)"$ 2 ????????????????????????????????????????????????????????????????????????????????? PSS
 "$./ -$)"/# $ 2 ??????????????????????????????????????????????????????????????????????????? PST
0 -4$)"$ 2 ????????????????????????????????????????????????????????????????????????????????? PST
<#4*/# -C ??????????????????????????????????????????????????????????????????????????????? PSU
I **().
# )*& #)" . ??????????????????????????????????????????????????????????????????? PSX
3(+' >0-***./* ????????????????????????????????????????????????????????????? PTO
I &" /. .
*$)"&)$( ???????????????????????????????????????????????????????????????????????????? PTR
ii
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# **( #)$. ???????????????????????????????????????????????????????????????????????? PTS
- /$)"/# /. .. / ?????????????????????????????????????????????????????????????? PTT
 '$)"$/# //)+"- . ????????????????????????????????????????????? PTV
4-$/ ????????????????????????????????????????????????????????????????????????????????????????? PTV
I &$)"+*0-**(
&0+) ./*- ?-<(+*-/)3+*-/? ?????????????????????????????????? PTX
#**.$)"/*-" -" / ????????????????????????????????????????????????????????????????? PUO
#$)&$)"*0/*0-)'* . ???????????????????????????????????????????????????????? PUO
 +$)"/'*.  ???????????????????????????????????????????????????????????????????????????????? PUP
(+*-/)3+*-/ #)$. ?????????????????????????????????????????????????????????? PUQ
# - / -*($' JK'/ -)/$1 ????????????????????????????????????????????????????? PUT
I $/ '$ )/.
/. ).+ /*- ???????????????????????????????????????????????????????????????????????????? PUV
-*2. -!*-$/ ??????????????????????????????????????????????????????????????????????? PVQ
'$++ - ????????????????????????????????????????????????????????????????????????????????????????????????? PVS
I $+# -!*-)-*$
)/-*0$)"$+# -!*-)-*$ ????????????????????????????????????????????????? PWR
0/$-./<*A* ($) - ???????????????????????????????????????????????????????????? PWS
# .$.*!$+# -!*-)-*$ ?????????????????????????????????????????????? PWW
# *./.*!$+# -!*-)-*$ ??????????????????????????????????????????????? PXO
I $+# -)..+#-. .
 ) -/$)"..+#-. ??????????????????????????????????????????????????????????????????? PXR
*'' /$)"..+#-. ????????????????????????????????????????????????????????????????????? PXW
0'/$A/*-0/# )/$/$*) ???????????????????????????????????????????????????????????? QOU
# $.&.*!/-$)" ????????????????????????????????????????????????????????????????????????????? QOV
I )"$)"$+# -
&0+) ./*- ?????????????????????????????????????????????????????????????????????????? QOX
$"-/$)"/*)-4+/$*) ????????????????????????????????????????????????????????????????????? QQP
I " **(0 -$ .
# -*' (>**0#/ ??????????????????????????????????????????????????????????? QQV
- ..$)"/#  ????????????????????????????????????????????????????????????????????????????? QQW
)/ -/# "$)"$--4 ??????????????????????????????????????????????????????????????????? QQX
"$)")**( ??????????????????????????????????????????????????????????????????????????????? QRO
I **(-*..-* .. .
**())1'$/$*)-&$)" ?????????????????????????????????????????????????????? QRV
)1'$/$*)-&$)")-* .. . ???????????????????????????????????????????????? QRW
)/-*0$)" )' 0'/$)./) )1'$/$*)JK ????????????????????????????? QRW
I -$"" -.
-$"" -.$. ????????????????????????????????????????????????????????????????????????????????????? QSR
**()-$"" -. ????????????????????????????????????????????????????????????????????????????? QSS
-$"" -./# -4 ?????????????????????????????????????????????????????????????????????? QSS
iii
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I #/D. 2$)**(C
-.$*)Q?R?3 ??????????????????????????????????????????????????????????????????????????????????????? QSX
iv
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Preface
#)&.B
#)&.!*-4*0-$)/ - ./$)**(B**($.**"' D..*'0/$*)!*-#$"#A' 1 '
/.  ..<!*-4*0-'*'$/ /. .?..0#<**(" /.'*/*!
// )/$*))$.- .*)'4+*+0'-?
#)&.'.*!*-4*0--* -$)/ - ./$))-*$++ 1 '*+( )/B)-*$$./#
(*./2$ '4A0. *+ -/$)".4./ (*)/# +') /<.*2 ) /* ' /*-+$'4
 1 '*+#$"#A,0'$/4)-*$++.?**()# '+2$/#/#/?
)/#)&.!*-4*0-$)/ - ./$)/#$.**&B - <4*0)' -)(*- *0/#*2/*
2*-&2$/#**(<!-*(/# .$./#-*0"#(*- *(+' 3. )-$*.?)<'*)"/#
24</# - (4 %*& *-/2*?
The Book’s Prerequisites
#$.**&$. .$") !*- 1 '*+ -.2$/#/' ./$/*!)-*$++ 1 '*+( )/
3+ -$ ) ?!4*0- !$-'4) 2/*)-*$<+' . *).$ -- $)" Elements of
Android Jetpack< Exploring Android<*-*/#< !*- *)/$)0$)"2$/#/#$.**&?
'.*)*/ /#//#$.**&D. 3(+' .- 2-$// )$)*/'$)?!4*0- 0)!($'$-2$/#
*/'$)<4*0)./$''' -)./07*0/**(!-*(/#$.**&</#*0"#$/2$'' (*-
$90'/?*0($"#/*).$ -- $)" Elements of Kotlin</*!($'$-$5 4*0-. '!2$/#
*/'$)?
v
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Source Code and Its License
# .*0- * $)/#$.**&$.'$ ). 0) -/# +# Q?O$ ). <$). 4*0
#1 /#  .$- /*- 0. )4*!$/?
*+4$)".*0- * $- /'4!-*(/# **&<$)/#  $/$*).<2*-&. ./2$/#
*   -</#*0"#$/(4'.*2*-&2$/#*/# -1$ 2 -.?*( 1$ 2 -.<
!*-- .*)./#/- ($)0)' -<!*0'0+*+4$)"/# .*0- * /*/# '$+*-
2# )$/$.. ' / ?
Acknowledgments
# 0/#*-2*0''$& /*/#)&)$ '$1 -<$6$/*4-<)/# - ./*!/#
 1 '*+ -./**"' - .+*).$' !*-**(?
# 0/#*-2*0''.*'$& /*/#)&/ +# )*(-*<$&-& -<)/# - ./*!
 / /$<+'0./#)-$ /.<).A-$./*+#/ $) -<)/# - ./*!/# 0-$)
-*% /<!*-''/# $-2*-&*)$+# -!*-)-*$)" ) -')-*$++
. 0-$/4?
PREFACE
vi
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Introductory Room
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room Basics
**"'  .-$ .**(.+-*1$$)"E)./-/$*)'4 -*1 -$/ /*''*2:0 )/
/.  ..2#$' #-) ..$)"/# !0''+*2 -*!$/ ?F
)*/# -2*-.<**($(./*(& 4*0-0. *!$/  .$ -</#-*0"#'$"#/2 $"#/
))*//$*)A. $(+' ( )//$*)*!) *% /A- '/$*)'(++$)"JK )"$) ?
Wrenching Relations Into Objects
!4*0#1  1 -2*-& 2$/#- '/$*)'/. H'$& $/ H!-*()*% /A
*-$ )/ ')"0" H'$& 1*-*/'$)H0)*0/ '44*0#1  )*0)/ -  /#
E*% /A- '/$*)'$(+ ) ($.(/#F?#/$.1 -4!)424*!.4$)"E$/D.
+$)" //$)"./07$)/*)*0/*!/# /. F?
)*% /A*-$ )/ +-*"-(($)"<2 - 0. /**% /.#*'$)"- ! - ) ./**/# -
*% /.<!*-($)".*( .*-/*!*% /"-+#?*2 1 -</-$/$*)'A./4' - '/$*)'
/. .2*-&*7*!/' .*!+-$($/$1 /<0.$)"!*- $")& 4.)%*$)/' ./*
3+- ..- '/$*).#$+.?$"0-$)"*0/#*2/*" /*0-'.. ./*(+/*- '/$*)'/' .
$.""-1/$)"<)$/0.0''4- .0'/.$)'*/*!*$' -+'/ * ?
-$/$*)')-*$ 1 '*+( )/0. . SQLiteDatabase !*-$)/ -/$)"2$/#$/ ?
#/<$)/0-)<0. . Cursor *% /./*- +- . )//# - .0'/.*!,0 -$ .)
ContentValues *% /./*- +- . )///* $). -/ *-0+/ ?#$' Cursor
) ContentValues - *% /.</# 4- !$-'4" ) -$<(0#$)/# 24/#/
HashMap *- ArrayList $." ) -$?)+-/$0'-<) $/# - Cursor )*- ContentValues
#.)4*!*0-0.$) ..'*"$? #1 /*.*( #*2 $/# -2-+/#/-*0)/#*.
*% /.*-*)1 -/ /2 )/#*. *% /.).*( *!*0-.?
#/'// -++-*#$.2#/*% /A- '/$*)'(++$)" )"$) .J.K/& ?
1
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/4+$'2*-&.*7*!1G*/'$)* ) $/# -" ) -/ ..0$/' /.
./-0/0- *-2*-&.2$/#4*0/*$ )/$!4#*2/# '.. ..#*0'(+/*.*(  3$./$)"
/' ./-0/0- J ?"?<' "4*) /#/4*0- ./0&2$/#K?# 0.0''4
" ) -/ ..*( * !*-4*0<).0++'$ .'$--4<2#$#$)*($)/$*)#$
(0#*!/# /.  /$'.!-*(4*0?
# ,0$)/ .. )/$'1$. $ -)/ ?*2 1 -<$ -)/ 2. 1 '*+ 2$/#
. -1 -A.$ 1$)($))$.)*/2 ''A.0$/ !*-.'$(+'/!*(.'$& )-*$
 1$ .?*2 1 -< 1./-*./ -*!)-*$.).$($'-'$--$ . #1  )
- / *1 -/# 4 -./*/-4/*8''/#/"+?*( *!/# (*- +*+0'-*) .#1
 )>
I  '$"#/
I '*2
I "- )
I -($/
I 0"-
**('.*# '+.2$/#/# *% /A- '/$*)'$(+ ) ($.(/#?/$.)*/. +*!
)..*( *!/# */# -.<.4*02$''  '$)"2$/#!$-$/?*2 1 -<
**(#.*) #0" 1)/" >$/$.!-*(**"' <)/# - !*- $/2$''  ( 
E*9$'F$)/#  4 .*!()4 1 '*+ -.)()" -.?
#$' /#$.**&$.!*0. *)**(<4*0(42$.#/* 3+'*- */# -.$!4*0-
$)/ - ./ $)0.$)"1G*/'$)*% /.0/.1$)"/# /$)$/ ?**($.
+*+0'-<0/$/$.!-!-*(/# *)'4*+/$*)?)+-/$0'-<$!4*0- $)/ - ./ $)
*/'$)G0'/$+'/!*-(!*--*..A+'/!*-( 1 '*+( )/<4*02$''2)//*'**&/
 '$"#/<.*4*0-/. *+ -/$*).)'.* -*..A+'/!*-(?
Room Requirements
*0. **(<4*0) /2* + ) )$ .$)4*0-(*0' D. build.gradle 8' >
P? # -0)/$( '$--4
Q? )))*//$*)+-* ..*-
)*/'$)+-*% /</#*. 2$'' >
I room-ktx</*+0''$)/# *- **(-0)/$( '$--$ .+'0..*( */'$)A
.+ $8 3/ ).$*).
ROOM BASICS
2
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I room-compiler<0. 2$/# kapt
*- 3(+' <$) /# NoteBasics (*0' *! /# **&D.+-$(-4.(+' +-*% /<2
#1  build.gradle 8' /#/+0''.$)/#*. /2*-/$!/.>
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled falsefalse
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.1"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "com.natpryce:hamkrest:1.7.0.0"
}
J!-*( */ .$.G0$'?"-' K
*/ /#/**(#. minSdkVersion - ,0$- ( )/*! 1 'PT*-#$"# -?!4*0
// (+//*0$'2$/#'*2 - minSdkVersion<4*02$''" /0$' --*-?!4*0/-4/*
*1 --$ **(D. minSdkVersion 0.$)"()$! ./( -" - ' ( )/.<2#$' /# +-*% /
2$''0$'< 3+ /**(/*-.##*--$'4?
Room Furnishings
*0"#'4.+ &$)"<4*0-0. *!**($.*($)/ 4/#- . /.*!'.. .>
P? )/$/$ .<2#$#- .$(+' '.. ./#/(* '/# /4*0- /-).! --$)"
ROOM BASICS
3
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
$)/*)*0/*!/# /.
Q? # / ..*% /JK</#/+-*1$ ./#  .-$+/$*)*!/# /#/
4*02)/!*-2*-&$)"2$/# -/$) )/$/$ .
R? # /. <2#$#/$ ./*" /# -''*!/#  )/$/$ .).!*-.$)"'
$/ /.
!4*0#1 0. ,0- D.  /-*8/<.*( *!/#$.2$''. (!($'$->
I # $.-*0"#'4)'*"*0./*4*0- /-*8/ interface *)2#$#4*0
 '- 4*0- . -1$ 
I *0- )/$/$ .- /# ./#/4*0-  3+ /$)".*)G*.#$G2#/ 1 -/*
- / . *)/#  . -1$ - .+*).
# NoteBasics (*0' ( )/$*) *1 #.! 2'.. .- '/ /*)*/ A/&$)"
++'$/$*)< 3 -$. 1$$)./-0( )/ / ./.?
Entities
)()4.4./ (.</#  )/$/4J*-/#/.4./ (D. ,0$1' )/K$..$(+' '../#/
4*0#++ )/*2)//*./*- $)/# /. ?/0.0''4- +- . )/..*( +-/*!4*0-
*1 -''*($)(* '<.*+4-*''.4./ (($"#/#1  )/$/$ .- +- . )/$)"
 +-/( )/.< (+'*4 .<)+4# &.?
$/#**(< // - .-$+/$*)*! )/$/$ .$./#//# 4- '.. .- +- . )/$)">
I /# //#/4*02)//*./*- $)/*/' <)
I /4+$'0)$/*!- .0'/. //#/4*0- /-4$)"/*- /-$ 1 !-*(/# /.
#/$7 - ) (4.*0) ($?/./-/./**( $)/*+'4$/(*- 2# )2
./-//#$)&$)"*0/ - '/$*).?
*2 1 -<$/'.*(*- '*. '4(/# ./# 24 /-*8/(+./* . -1$ .?$/#
 /-*8/<2 - )*/ .-$$)"/# *)/ )/.*!/#  . -1$ D./. ?/# -<2
-  .-$$)"#*22 2)//*2*-&2$/# 8)  . -1$  )+*$)/.?#*.
)+*$)/.#1 +-/$0'-. /*!*)/ )//#/2 )2*-&2$/#<*0-/ .4*!
2#* 1 - 1 '*+ /#  . -1$ ? - .$(+'4(++$)"/#*. /*( /#*.)
'.. .<*/#!*-$)+0/)*0/+0/?**($..*( 2# - $) /2 ) /-*8/A./4'
E2 %0.//& 2#//#  . -1$ "$1 .0.F++-*#)!0''A./4' E2
*)/-*' 1 -4/#$)"*0//# /. F++-*#?
-*(*$)"./)+*$)/<) )/$/4$.1G*/'$)'..(-& 2$/#/# @Entity
ROOM BASICS
4
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
))*//$*)?*- 3(+' <# - $. NoteEntity '../#/. -1 ..**( )/$/4>
packagepackage com.commonsware.room.notescom.commonsware.room.notes
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "notes")
data classdata class NoteEntityNoteEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval text: StringString,
valval version: IntInt
)
J!-*( */ .$.G.-G($)G%1G*(G*((*).2- G-**(G)*/ .G*/ )/$/4?&/K
# - $.)*+-/$0'-.0+ -'..- ,0$- !*- )/$/$ .<)/#  3+ //$*)$./#/
*!/ )/# 42$'' .$(+' data '.. .<.2 . # - ?
# @Entity ))*//$*))#1 +-*+ -/$ .0./*($5$)"/#  #1$*-*!4*0- )/$/4
)#*2**(2*-&.2$/#$/?)/#$.. <2 #1  tableName +-*+ -/4?#  !0'/
)( *!/# $/ /' $./# .( ./#  )/$/4'..)( <0/ tableName ''*2.
4*0/**1 --$ /#/).0++'44*0-*2)/' )( ? - <2 *1 --$ /# /'
)( /* notes?
*( /$( .<4*0-+-*+ -/$ .2$'' (-& 2$/#))*//$*). .-$$)"/# $--*' .?
)/#$. 3(+' </# id 8 '#./# @PrimaryKey ))*//$*)</ ''$)"**(/#//#$.
$./# 0)$,0 $ )/$8 -!*-/#$. )/$/4?**(2$''0. /#//*&)*2#*2/*0+/
) ' / Note *% /.4/# $-+-$(-4& 41'0 .?)1<**('.*- ,0$- ./#/
)4 @PrimaryKey 8 '*!)*% //4+ H'$& String H ))*// 2$/#
@NonNull<.+-$(-4& 4.$)$/ ))*/ null?)*/'$)<4*0)%0./0. 
)*)A)0''' /4+ <.0#. String?
2$'' 3+'*-  )/$/$ .$)"- / - /$'$) )0+*($)"#+/ -?
DAO
E/ ..*% /FJK$.!)424*!.4$)"E/# $)/*/# /F?# $ 
$./#/4*0#1 /#/+-*1$ .( /#*.!*-/# /. *+ -/$*)./#/4*0
) >,0 -$ .<$). -/.<0+/ .< ' / .<).**)?
)**(</# $.$ )/$8 4/# @Dao ))*//$*)<++'$ /* $/# -)
ROOM BASICS
5
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
abstract '..*-) interface?# /0'*)- / $(+' ( )//$*)2$'' * A
" ) -/ !*-4*04/# **())*//$*)+-* ..*-?
# +-$(-4-*' *!/# @DaoA))*//  abstract '..*- interface $./*#1 *)
*-(*- ( /#*.<2$/#/# $-*2)**())*//$*).<$ )/$!4$)"2#/4*02)//*
*2$/#/# /. )4*0- )/$/$ .?#$.. -1 ./# .( -*' ./# !0)/$*).
))*//  @GET *- @POST $) /-*8/$)/ -! ?
# .(+' ++#. NoteStore /#/$.*0->
packagepackage com.commonsware.room.notescom.commonsware.room.notes
importimport androidx.room.*androidx.room.*
@Dao
interfaceinterface NoteStoreNoteStore {
@Query("SELECT * FROM notes")
funfun loadAll(): ListList<NoteEntityNoteEntity>
@Insert
funfun insert(note: NoteEntityNoteEntity)
@Update
funfun update(note: NoteEntityNoteEntity)
@Delete
funfun delete(varargvararg notes: NoteEntityNoteEntity)
}
J!-*( */ .$.G.-G($)G%1G*(G*((*).2- G-**(G)*/ .G*/ /*- ?&/K
 .$ ./# @Dao ))*//$*)*)/# NoteStore $)/ -! <2 #1 !*0-!0)/$*).<
#2$/#/# $-*2)))*//$*).> @Query< @Insert< @Update<) @Delete< #
2#$#(+/*/# *-- .+*)$)"/. *+ -/$*).?
# loadAll() !0)/$*)#./# @Query ))*//$*)?-$)$+''4< @Query 2$'' 0. 
!*- SELECT .// ( )/.<2# - 4*0+0//# /0'$)/# ))*//$*)$/. '!?
 - <2 - - /-$ 1$)" 1 -4/#$)"!-*(/# notes /' ?
# - ($)$)"/#- !0)/$*).0. /# @Insert< @Update<) @Delete ))*//$*).<
(++ /*!0)/$*).*!/# .( )( ?# /0'!0)/$*))( .*)*/(// -H
/# 4*0' larry()< curly()<) moe() )2*-&%0./.2 ''?.4*0($"#/
3+ /< @Insert $). -/.) )/$/4$)/**0-/' < @Update 0+/ .) 3$./$)"/'
-*2/*- : //# .0++'$  )/$/4D.+-*+ -/$ .<) @Delete  ' / ./' -*2.
ROOM BASICS
6
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*-- .+*)$)"2$/#/# .0++'$  )/$/$ .D+-$(-4& 4.?)/#$..(+' < insert() )
update() #/& .$)"' NoteEntity<2#$' delete() /& . vararg *!
NoteEntity?**(.0++*-/. $/# -+// -)<.2 ''.*/# -.<.0#. List *!
NoteEntity H#**. 2#/8/.4*0-) .?
2$'' 3+'*- /# $)"- / - /$'$) )0+*($)"#+/ -?
Database
)$/$*)/* )/$/$ .).<4*02$''#1 /' ./*) @DatabaseA))*// 
abstract '..< 3/ )$)" RoomDatabase . '..?#$.'..&)$/./*" /# -/#
/. 8' </#  )/$/$ .<)/# .?
)/# .(+' +-*% /<2 #1  NoteDatabase . -1$)"/#$.-*' >
packagepackage com.commonsware.room.notescom.commonsware.room.notes
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
@Database(entities = [NoteEntityNoteEntity::classclass], version = 1)
abstractabstract classclass NoteDatabaseNoteDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun notes(): NoteStoreNoteStore
}
J!-*( */ .$.G.-G($)G%1G*(G*((*).2- G-**(G)*/ .G*/ /. ?&/K
# @Database ))*//$*)*)8"0- ./# * " ) -/$*)+-* ..<$)'0$)">
I  )/$!4$)"''*!/#  )/$/4'.. ./#/4*0- *0/$)/# entities
*'' /$*)
I  )/$!4$)"/# .# (1 -.$*)*!/# /. J.4*0. 2$/#
SQLiteOpenHelper $)*)1 )/$*)')-*$$/  1 '*+( )/K
 - <2 - .4$)"/#/2 #1 %0./*)  )/$/4'..JNoteEntityK<)/#//#$.$.
.# (1 -.$*)P?
*0'.*)  abstract !0)/$*).!*- #'../#/- /0-))$)./) *!/#/
'..? - <2 #1  notes() !0)/$*)/#/- /0-). NoteStore?
ROOM BASICS
7
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Get a Room
0- NoteDatabase $.) abstract '..?*( 2# - </#*0"#<2 ) /*" /)
$)./) *!$/<.*2 )'' notes() ) ' /*./-/()$+0'/$)"/# /. ?
*- /  NoteDatabase<4*0)  RoomDatabase.Builder?# - - /2*
!0)/$*).*)/# Room '..!*-" //$)"*) >
I databaseBuilder()<)
I inMemoryDatabaseBuilder()
databaseBuilder() 2$''# '+4*0- / /. & 4/-$/$*)'$/
/. 8' ? inMemoryDatabaseBuilder() - / .$/ /. 2#*.
*)/ )/.- *)'4./*- $)( (*-4H..**)./# /. $.'*. </#
( (*-4#*'$)"/# /. *)/ )/." /.!- ?
*/#!0)/$*)./&  Context )/# 1 Class *% /*!4*0- RoomDatabase
.0'...+-( / -.? databaseBuilder() '.*/& ./# )( *!/# /. 8'
/*0. ?
*<2 *0'- / - "0'-<8' A& NoteDatabase 1$>
privateprivate valval db =
RoomRoom.databaseBuilder(context, NoteDatabaseNoteDatabase::classclass.java, "notes.db").build()
J2# - context $..0$/' Context<.0#./# Application .$)"' /*)K
#$' /# - - .*( *)8"0-/$*)( /#*./#/) '' *)/#
RoomDatabase.Builder<2 .&$+/#*. # - <.$(+'4''$)" build() /*0$'/#
NoteDatabase<..$")$)"$//*/# db +-*+ -/4?
-*(/# - <2 )>
I '' notes() *)/# NoteDatabase /*- /-$ 1 /# NoteStore
I ''( /#*.*)/# NoteStore /*,0 -4<$). -/<0+/ <*- ' /
NoteEntity *% /.
Testing Room
) 4*0#1  RoomDatabase )$/...*$/ J.K) )/$/$ .. /0+<4*0
ROOM BASICS
8
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
.#*0'./-// ./$)"$/?
# "**) 2.$./#// ./$)"**($.)*/-(/$''4$7 - )//#)$./ ./$)"
)4/#$)" '. $))-*$?**(#.! 2#-/ -$./$./#/(& $/$/ .$ -
/#).*( /#$)"./*/ ./<.$//0-).*0/?
*0)' -)(*- *0// ./$)"$)/# N ./$)"*0-#)" .N
#+/ -*! Elements of Android JetpackB
Writing Instrumented Tests
)/# 2#*' <2-$/$)"$)./-0( )/ / ./.!*-**(H2# - /# / ./.-0)*))
)-*$ 1$ *- (0'/*-H$.0)- (-&' ?*0" /)$)./) *!4*0-
RoomDatabase .0'..) 3 -$. $/!-*(/# - ?
*<!*- 3(+' <# - $.)$)./-0( )/ / ./. '../* 3 -$. /# NoteDatabase>
packagepackage com.commonsware.room.notescom.commonsware.room.notes
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.hasSizecom.natpryce.hamkrest.hasSize
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport java.util.*java.util.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass NoteStoreTestNoteStoreTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
NoteDatabaseNoteDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.notes()
@Test
funfun insertAndDelete() {
assertThat(underTest.loadAll(), isEmpty)
ROOM BASICS
9
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
valval entity = NoteEntityNoteEntity(
id = UUIDUUID.randomUUID().toString(),
title = "This is a title",
text = "This is some text",
version = 1
)
underTest.insert(entity)
underTest.loadAll().let {
assertThat(it, hasSize(equalTo(1)))
assertThat(it[0], equalTo(entity))
}
underTest.delete(entity)
assertThat(underTest.loadAll(), isEmpty)
}
@Test
funfun update() {
valval entity = NoteEntityNoteEntity(
id = UUIDUUID.randomUUID().toString(),
title = "This is a title",
text = "This is some text",
version = 1
)
underTest.insert(entity)
valval updated = entity.copy(title = "This is new", text = "So is this")
underTest.update(updated)
underTest.loadAll().let {
assertThat(it, hasSize(equalTo(1)))
assertThat(it[0], equalTo(updated))
}
}
}
J!-*( */ .$.G.-G)-*$ ./G%1G*(G*((*).2- G-**(G)*/ .G*/ /*-  ./?&/K
Using In-Memory Databases
# )/ ./$)"/. </#*0"#<*) *!/# #'' )" .$.$)(&$)"/#*. / ./.
ROOM BASICS
10
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
E# -( /$F<*-. '!A*)/$) ?) / ./( /#*.#*0')*/ + )0+*))*/# -/ ./
( /#*<)*) / ./( /#*.#*0')*/7 //# - .0'/.*!)*/# -/ ./( /#*
$ )/''4?#$.( )./#/2 2)//*./-/2$/#&)*2)./-/$)"+*$)/ !*-
#/ ./<)2 #1 /**).$ -#*2/**/#/?
) ++-*#H/# *) /& )$)/# *1 NoteStoreTest '..H$./*0. )$)A
( (*-4/. ?# db +-*+ -/4$.$)$/$'$5 0.$)"
Room.inMemoryDatabaseBuilder()<.*2 " /*0-!./<$.+*.' $)A( (*-4
/. ?*-*)/ 3/<2 0.
InstrumentationRegistry.getInstrumentation().targetContext< Context !*-
/# *  $)"/ ./ ? /# ). /0+ underTest /* /# *% //#/2 - / ./$)">
/# NoteStore )$/.!0)/$*).?
# - - /2*& 41)/" .!*-0.$)")$)A( (*-4/. !*-$)./-0( )/ 
/ ./$)">
P? /$.$)/-$).$''4. '!A*)/$) ?) /# NoteDatabase $.'*. J*-
"-" A*'' / K<$/.( (*-4$.- ' . <)$!. +-/ / ./.0. . +-/
NoteDatabase $)./) .<*) 2$'')*/7 //# */# -?
Q?  $)")2-$/$)"/*)!-*(( (*-4$.(0#!./ -/#)$.- $)")
2-$/$)"/*)!-*($.&<.*/# / ./.-0)(0#!./ -?
)/# */# -#)</#$.( )./#//# $)./-0( )/ / ./.- 0. ' ..!*-
+ -!*-() / ./$)"<.J+- .0('4K4*0-+-*0/$*)++2$''/0''4./*- $/.
/. *)$.&?*0*0'0. -' *(()A'$) .2$/# .<0./*(0$'/4+ .
) buildConfigField<*-*/# -( )./* $ 2# )/ ./.- -0)2# /# -/# 4
.#*0'0. ( (*-4*-$.&?
The Test Functions
0-/ ./!0)/$*).*/#$)".'$& >
I - /$)" NoteEntity $)./) .<0.$)"!*-/# id
I ''$)" insert()< update()<) delete() /*()$+0'/ /# /' *)/ )/.
I ''$)" loadAll() /*- /-$ 1 2#/$.$)/# /'
)<'*)"/# 24<2 0. (&- ./(/# -. /**)8-(/#/ 1 -4/#$)"$.
2*-&$)".2  3+ /?
ROOM BASICS
11
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
The Dao of Entities
)/# +- 1$*0.#+/ -<2 2 )//#-*0"#/# .$./ +.!*-. //$)"0+**(>
I - / )))*// 4*0- )/$/4'.. .
I - / <))*// <) 8) *+ -/*-!0)/$*).*)4*0-J.K
I - / .0'..*! RoomDatabase /*/$ /#  )/$/$ .)J.K/*" /# -
I - / )$)./) *!/#/ RoomDatabase /.*( '$& '4+*$)/$)/$( <
I . /# RoomDatabase $)./) /*- /-$ 1 4*0-)!-*(/# - 2*-&
2$/#4*0- )/$/$ .
*2 1 -<2 *)'4.-/# /# .0-! *!2#/) *)8"0- *) )/$/$ .)
.?)/#$.#+/ -<2 2$''./-//* 3+'*- /# - ./*!/# *)8"0-/$*)!*-
)/$/$ .).?
)4*!/# * .)$++ /..#*2)$)/#$.#+/ -*( !-*(/# /# MiscSamples
(*0' *! /# **&D.+-$(-4.(+' +-*% /<.(+' +-*% /?#$.$.'$--4
(*0' 2$/#1-$ /4*! )/$/$ .).<''/$ $)/* MiscDatabase<2$/#
$)./-0( )/ / ./.!*- #*!/#  )/$/$ .?
Configuring Entities
# *)'4.*'0/ - ,0$- ( )/.!*-**( )/$/4'..$./#/$/ ))*// 2$/#
/# @Entity ))*//$*))#1 8 '$ )/$8 ./# +-$(-4& 4</4+$''44
24*! @PrimaryKey ))*//$*)?)4/#$)"*1 ) 4*)/#/$.*+/$*)'?
*2 1 -</# - $.!$-$//#/$.E*1 ) 4*)/#/F?*( H/#*0"#+-*'4
)*/''H*!/# . ! /0- .2$'' *!$)/ - ./$)'-" -++.?
13
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Primary Keys
!4*0#1 .$)"' 8 '/#/$./# +-$(-4& 4!*-4*0- )/$/4<0.$)"/#
@PrimaryKey ))*//$*)$..$(+' )# '+.4*0' -'4$ )/$!4/#/+-$(-4& 4/
'/ -+*$)/?
*2 1 -<4*0*#1 .*( */# -*+/$*).?
Auto-Generated Primary Keys
)$/ <$!4*0#1 ) INTEGER *'0()$ )/$8 ./# PRIMARY KEY<4*0)
*+/$*)''4#1 $/ ..$")0)$,0 1'0 .!*-/#/*'0()<424*!/#
AUTOINCREMENT & 42*-?
)**(<$!4*0#1 ) Long +-*+ -/4/#/$.4*0- @PrimaryKey<4*0)*+/$*)''4
++'4 AUTOINCREMENT /*/# *-- .+*)$)"*'0()4$)" autoGenerate=true /*
/# ))*//$*)>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
@Entity(tableName = "autoGenerate")
data classdata class AutoGenerateEntityAutoGenerateEntity(
@PrimaryKey(autoGenerate = truetrue)
varvar id: LongLong,
varvar text: StringString
) {
@Dao
abstractabstract classclass StoreStore {
@Query("SELECT * FROM autoGenerate")
abstractabstract funfun loadAll(): ListList<AutoGenerateEntityAutoGenerateEntity>
@Query("SELECT * FROM autoGenerate WHERE id = :id")
abstractabstract funfun findById(id: IntInt): AutoGenerateEntityAutoGenerateEntity
funfun insert(entity: AutoGenerateEntityAutoGenerateEntity): AutoGenerateEntityAutoGenerateEntity {
entity.id = _insert(entity)
returnreturn entity
}
@Insert
THE DAO OF ENTITIES
14
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
abstractabstract funfun _insert(entity: AutoGenerateEntityAutoGenerateEntity): LongLong
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0/* ) -/ )/$/4?&/K
4 !0'/< autoGenerate $. false? //$)"/#/+-*+ -/4/* true "$1 .4*0
AUTOINCREMENT $)/# " ) -/  CREATE TABLE .// ( )/>
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS autoGenerate (id INTEGER PRIMARYPRIMARY KEYKEY AUTOINCREMENT NOTNOT
NULLNULL, text TEXT NOTNOT NULLNULL)
*2 1 -</#$../-/./*" /*(+'$/ $)/# ++?*0*)*/&)*24*0-+-$(-4
& 40)/$'4*0$). -//#  )/$/4$)/*/. ?*0- @InsertA))*// !0)/$*).
)- /0-) Long - .0'/<)/#/2$'' /# +-$(-4& 4!*-/#/$). -/  )/$/4?)
/# AutoGenerateEntity .#*2)*1 < _insert() #./# @Insert ))*//$*)<2#$'
insert() 2-+. _insert() ). /./# $). -/  )/$/4D. id /* /# Long - /0-) 
4 _insert()? ) < insert() 0+/ ./#  )/$/4/*#1 $/.+-$(-4& 4>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport org.junit.Assert.*org.junit.Assert.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass AutoGenerateEntityTestAutoGenerateEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.autoGenerate()
@Test
funfun autoGenerate() {
assertThat(underTest.loadAll(), isEmpty)
valval original = AutoGenerateEntityAutoGenerateEntity(id = 0, text = "This will get its own ID")
valval inserted = underTest.insert(original)
assertTrue(original === inserted)
assertThat(inserted.id, !equalTo(0L))
}
}
THE DAO OF ENTITIES
15
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G0/* ) -/ )/$/4 ./?&/K
#$.+- . )/.E/-$&' A*2)F*(+'$/$*).H!*- 3(+' <4*0))*/(& /#
+-$(-4& 4+-*+ -/4 val<./# )4*0))*/- / )$)./) *!) )/$/4/#/
$.)*/4 /$)/# /. ?
*( *!/# .(+' .$)/#$.**&2$''0.  UUID $)./ ?#$' /# . /& 0+(0#
(*- -**(/#).$(+' Long</# 4) 0)$,0 '4" ) -/ *0/.$ *!/#
/. ?*-4*0-+-*0/$*)++.<4*02$'') /* $ $!/# # # .
.0--*0)$)"/. A" ) -/ $ )/$8 -.- 2*-/#/# $- ) 8/.?
Composite Primary Keys
).*( . .<4*0(4#1 *(+*.$/ +-$(-4& 4<( 0+*!/2**-(*-
*'0().$)/# /. ?#$.$.+-/$0'-'4/-0 $!4*0- /-4$)"/* .$")4*0-
)/$/$ .-*0)) 3$./$)"/. ./-0/0- <*) /#/0. *(+*.$/ +-$(-4
& 4!*-*) *!$/./' .J!*-2#/ 1 -- .*)K?
!<'*"$''4</#*. - ''+-/*!.$)"' *% /<4*0*0'*($) /# ($)/*
.$)"' +-*+ -/4<.2 2$''. $) /# ) 3/#+/ -?*2 1 -<$/(4 /#//# 4
.#*0' $)$1$0'+-*+ -/$ .$)4*0- )/$/4<0//# 4#++ )/**($) /*- /
/# +-$(-4& 4?)/#/. <4*0).&$+/# @PrimaryKey ))*//$*))0. /#
primaryKeys +-*+ -/4*!/# @Entity ))*//$*)?
) . )-$*!*-/#$.$./1 -.$*)$)"<2# - 2 - /-&$)"#)" ./*/*1 -
/$( </# 241 -.$*)*)/-*'.4./ (/-&.#)" ./*.*0- * )*/# -8' .
*1 -/$( ?# - - . 1 -'24.*!$(+' ( )/$)"/1 -.$*)$)"?) ++-*#
#.''1 -.$*).*!/# .(  )/$/4$)/# .( /' <2$/#1 -.$*)* //# /*
/# E)/0-'F+-$(-4& 4/*$ )/$!4.+ $81 -.$*)*!/#/*)/ )/?)/#/. <
4*0*0'#1 .*( /#$)"'$& >
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.Insertandroidx.room.Insert
importimport androidx.room.Queryandroidx.room.Query
@Entity(tableName = "compositeKey", primaryKeys = ["id", "version"])
data classdata class CompositeKeyEntityCompositeKeyEntity(
valval id: StringString,
valval title: StringString,
valval text: StringString = "",
THE DAO OF ENTITIES
16
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
valval version: IntInt = 1
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM compositeKey")
funfun loadAll(): ListList<CompositeKeyEntityCompositeKeyEntity>
@Query("SELECT * FROM compositeKey where id = :id AND version = :version")
funfun findByPrimaryKey(id: StringString, version: IntInt): CompositeKeyEntityCompositeKeyEntity
@Insert
funfun insert(entity: CompositeKeyEntityCompositeKeyEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*(+*.$/  4)/$/4?&/K
**(2$''/# )0. /# PRIMARY KEY & 42*-$)/# CREATE TABLE .// ( )//*. /
0+/# *(+*.$/ +-$(-4& 4>
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS compositeKey (id TEXT NOTNOT NULLNULL, title TEXT NOTNOT NULLNULL, text
TEXT NOTNOT NULLNULL, versionversion INTEGER NOTNOT NULLNULL, PRIMARYPRIMARY KEYKEY(id, versionversion))
)*0-. <2 . / version /*#1  !0'/1'0 *! 1<.*2 )- / 
CompositeKeyEntity 2$/#%0././-$)"$ )/$8 -</' ./!*-$/.$)$/$'1 -.$*)>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport android.database.sqlite.SQLiteConstraintExceptionandroid.database.sqlite.SQLiteConstraintException
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport java.util.*java.util.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass CompositeKeyEntityTestCompositeKeyEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.compositeKey()
THE DAO OF ENTITIES
17
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@Test
funfun compositeKey() {
assertThat(underTest.loadAll(), isEmpty)
valval original = CompositeKeyEntityCompositeKeyEntity(
id = UUIDUUID.randomUUID().toString(),
title = "A composite key entity"
)
underTest.insert(original)
underTest.loadAll().let {
assertThat(it.size, equalTo(1))
assertThat(it[0], equalTo(original))
}
assertThat(
underTest.findByPrimaryKey(
id = original.id,
version = original.version
), equalTo(original)
)
}
@Test(expected = SQLiteConstraintExceptionSQLiteConstraintException::classclass)
funfun duplicateCompositeKey() {
assertThat(underTest.loadAll(), isEmpty)
valval original = CompositeKeyEntityCompositeKeyEntity(
id = UUIDUUID.randomUUID().toString(),
title = "A composite key entity"
)
underTest.insert(original)
valval copy = original.copy(text = "This is different!")
underTest.insert(copy)
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G*(+*.$/  4)/$/4 ./?&/K
!2 /-4/*$). -/ )/$/$ .2$/#/# .( & 4/2$ <*0- @InsertA))*// !0)/$*)
2$''/#-*2 SQLiteConstraintException?
THE DAO OF ENTITIES
18
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Adding Indexes
*0-+-$(-4& 4$.$) 3 0/*(/$''44$/ ?*2 1 -<4*0(42$.#/*. /
0+*/# -$) 3 .!*-*/# -*'0().*-*'' /$*).*!*'0().</*.+ 0+,0 -$ .?
**/#/<4*0#1 /2*#*$ .>
P? . /# indices +-*+ -/4*) @Entity?#$.+-*+ -/4/& .'$./*!) ./ 
Index ))*//$*).< #*!2#$# '- .)$) 3?
Q? . /# index +-*+ -/4*) @ColumnInfo</*)$) 3*).$)"'
+-*+ -/4?
# '// -$..$(+' -=/# !*-( -#)' .(*- *(+' 3. )-$*.J ?"?<)$) 3
$)1*'1$)"(0'/$+' +-*+ -/$ .K?
 - <2 #1 ) )/$/42$/#)$) 3*) category +-*+ -/4>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
@Entity(tableName = "indexified")
data classdata class IndexedEntityIndexedEntity(
@PrimaryKey
valval id: StringString,
valval title: StringString,
@ColumnInfo(index = truetrue) valval category: StringString,
valval text: StringString? = nullnull,
valval version: IntInt = 1
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM indexified")
funfun loadAll(): ListList<IndexedEntityIndexedEntity>
@Query("SELECT * FROM indexified where category = :category")
funfun loadAllForCategory(category: StringString): ListList<IndexedEntityIndexedEntity>
@Insert
funfun insert(varargvararg entity: IndexedEntityIndexedEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G) 3 )/$/4?&/K
**(2$''/# - ,0 ./ $) 3>
THE DAO OF ENTITIES
19
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS indexified (id TEXT NOTNOT NULLNULL, title TEXT NOTNOT NULLNULL,
category TEXT NOTNOT NULLNULL, text TEXT, versionversion INTEGER NOTNOT NULLNULL, PRIMARYPRIMARY KEYKEY(id))
CREATECREATE INDEXINDEX IF NOTNOT EXISTSEXISTS index_indexified_category ONON indexified (category)
'/ -)/$1 '4<2 *0'#1 0.  indices *)/# @Entity ))*//$*)>
@Entity(tableName = "indexified", indices = [IndexIndex("category")])
classclass IndexedEntityIndexedEntity(
@PrimaryKey
valval id: StringString,
valval title: StringString,
valval category: StringString,
valval text: StringString? = nullnull,
valval version: IntInt = 1
)
!4*0#1 *(+*.$/ $) 3<*).$./$)"*!/2**-(*- 8 '.</# Index ) ./ 
))*//$*)/& .*((A '$($/ '$./*!*'0())( .)2$''" ) -/ /#
*(+*.$/ $) 3?
# $) 32$'' 0. 4$/ 0/*(/$''4$!4*0 3 0/ ,0 -$ ./#/$)1*'1
/# $) 3?# loadAllForCategory() !0)/$*),0 -$ .*)/# $) 3  category
+-*+ -/4<).**0-$) 3.#*0' 0. 2# )2 ''/#/!0)/$*)>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.hasSizecom.natpryce.hamkrest.hasSize
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport java.util.*java.util.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass IndexedEntityTestIndexedEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
THE DAO OF ENTITIES
20
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
privateprivate valval underTest = db.indexed()
@Test
funfun queryByCategory() {
assertThat(underTest.loadAll(), isEmpty)
valval funStuff = IndexedEntityIndexedEntity(
id = UUIDUUID.randomUUID().toString(),
title = "This is fun!",
category = "fun-stuff",
text = "words words words"
)
valval notAsFunStuff = IndexedEntityIndexedEntity(
id = UUIDUUID.randomUUID().toString(),
title = "Gloom, despair, and agony on me",
category = "un-fun-stuff"
)
underTest.insert(funStuff, notAsFunStuff)
underTest.loadAllForCategory("fun-stuff").let {
assertThat(it, hasSize(equalTo(1)))
assert(it[0] == funStuff)
}
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G) 3 )/$/4 ./?&/K
!/# $) 3.#*0''.* )!*- 0)$,0 ) ..H*)'4*)  )/$/4)#1 /# $) 3
1'0 H unique = true /*/# Index ))*//$*)?#$.- ,0$- .4*0/*..$")/#
*'0()J.K!*-/# $) 3/*/# value +-*+ -/4<0 /*/# 24))*//$*).2*-&$)
*/'$)>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
@Entity(
tableName = "uniquelyIndexed",
indices = [IndexIndex(value = ["title"], unique = truetrue)]
)
data classdata class UniqueIndexEntityUniqueIndexEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval text: StringString = "",
valval version: IntInt = 1
THE DAO OF ENTITIES
21
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM uniquelyIndexed")
funfun loadAll(): ListList<UniqueIndexEntityUniqueIndexEntity>
@Insert
funfun insert(entity: UniqueIndexEntityUniqueIndexEntity)
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.ABORTABORT)
funfun insertOrAbort(entity: UniqueIndexEntityUniqueIndexEntity)
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.IGNOREIGNORE)
funfun insertOrIgnore(entity: UniqueIndexEntityUniqueIndexEntity)
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
funfun insertOrReplace(entity: UniqueIndexEntityUniqueIndexEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G)$,0 ) 3)/$/4?&/K
#$.0. .**(/*/# UNIQUE & 42*-/*/# CREATE INDEX .// ( )/>
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS uniquelyIndexed (id TEXT NOTNOT NULLNULL, title TEXT NOTNOT NULLNULL,
text TEXT NOTNOT NULLNULL, versionversion INTEGER NOTNOT NULLNULL, PRIMARYPRIMARY KEYKEY(id))
CREATECREATE UNIQUEUNIQUE INDEXINDEX IF NOTNOT EXISTSEXISTS index_uniquelyIndexed_title ONON uniquelyIndexed
(title)
#$' - "0'-$) 3.0++*-/.(0'/$+' 1'0 .<0)$,0 $) 3* .)*/<' $)"
*) "$)/* SQLiteConstraintException $!2 /-4$). -/$)"0+'$/ >
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport android.database.sqlite.SQLiteConstraintExceptionandroid.database.sqlite.SQLiteConstraintException
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.*com.natpryce.hamkrest.*
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport java.util.*java.util.*
privateprivate constconst valval TEST_TITLE = "A Tale of Two Entities"
THE DAO OF ENTITIES
22
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass UniqueIndexEntityTestUniqueIndexEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.uniqueIndex()
@Test
funfun singleInsert() {
assertThat(underTest.loadAll(), isEmpty)
valval firstEntity = UniqueIndexEntityUniqueIndexEntity(
id = UUIDUUID.randomUUID().toString(),
title = TEST_TITLETEST_TITLE,
text = "This entity will get inserted successfully")
underTest.insert(firstEntity)
assertThat(
underTest.loadAll(),
allOf(hasSize(equalTo(1)), hasElement(firstEntity))
)
}
@Test(expected = SQLiteConstraintExceptionSQLiteConstraintException::classclass)
funfun duplicateFailure() {
singleInsert()
valval secondEntity = UniqueIndexEntityUniqueIndexEntity(
id = UUIDUUID.randomUUID().toString(),
title = TEST_TITLETEST_TITLE,
text = "This entity is doomed")
underTest.insert(secondEntity)
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G)$,0 ) 3)/$/4 ./?&/K
Ignoring Properties
!/# - - +-*+ -/$ .$)/#  )/$/4'../#/.#*0')*/ + -.$./ <4*0)
))*// /# (2$/# @Ignore>
THE DAO OF ENTITIES
23
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
@Entity(tableName = "ignoredProperty")
data classdata class IgnoredPropertyEntityIgnoredPropertyEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval version: IntInt = 1
) {
@Ignore varvar text: StringString = ""
varvar moreText: StringString = ""
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM ignoredProperty")
funfun loadAll(): ListList<IgnoredPropertyEntityIgnoredPropertyEntity>
@Query("SELECT * FROM ignoredProperty where id = :id")
funfun findByPrimaryKey(id: StringString): IgnoredPropertyEntityIgnoredPropertyEntity
@Insert
funfun insert(entity: IgnoredPropertyEntityIgnoredPropertyEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G")*- -*+ -/4)/$/4?&/K
*0($"#//#$)&/#/4*0*0'.&$+/#/)0. */# -/ #)$,0 .<.0#.40.$)"
private val +-*+ -/4>
@Entity(tableName = "ignoredProperty")
data classdata class IgnoredPropertyEntityIgnoredPropertyEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval version: IntInt = 1
) {
privateprivate valval text: StringString = ""
varvar moreText: StringString = ""
}
$) /# text +-*+ -/4$. private )#.)*. // -<*) *0'-"0 /#/**(
($"#/$")*- $/0/*(/$''4?**(<$)./ <" ) -/ .0$' --*-<.$/))*// ''
$!4*02)//*$")*- /#/+-*+ -/4*-$!4*0.$(+'4!*-"*//*$/+-*+ -'4?
)*/# -*+/$*)<$)./ *! @Ignore<$./*0. @Transient<$!/#/))*//$*)8/.4*0-
THE DAO OF ENTITIES
24
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
) . // ->
@Entity(tableName = "ignoredProperty")
data classdata class IgnoredPropertyEntityIgnoredPropertyEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval version: IntInt = 1
) {
@Transient varvar text: StringString = ""
varvar moreText: StringString = ""
}
/#$-*+/$*)$./*0. ignoredColumns<+-*+ -/4$)/# @Entity ))*//$*)</#/
/& .)--4*!*'0())( ./#/.#*0' $")*- >
@Entity(
tableName = "ignoredProperty",
ignoredColumns = ["text"]
)
data classdata class IgnoredPropertyEntityIgnoredPropertyEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval version: IntInt = 1,
varvar text: StringString = ""
) {
varvar moreText: StringString = ""
}
Custom Column Names
4 !0'/<**(2$''" ) -/ )( .!*-4*0-/' .)*'0().. *7*!/#
)/$/4'..)( .)+-*+ -/4)( .?)" ) -'<$/* .- .+ /' %**!/#$.<
).*4*0(4%0./' 1 /# ('*) ?*2 1 -<4*0(48)/#/4*0) /*
*)/-*'/# . )( .<+-/$0'-'4$!4*0- /-4$)"/*(/#) 3$./$)"/.
.# (J ?"?<4*0- ($"-/$)") 3$./$)")-*$++/*0. **($)./ *!
0.$)"$/ $- /'4K?)!*-/' )( .$)+-/$0'-<. //$)"4*0-*2))( )
.$(+'$!4.*( *!/# /#/4*0#1 /*2-$/ !*- @QueryA))*// !0)/$*).?
.2 #1 . )</**)/-*'/# /' )( <0. /# tableName +-*+ -/4*)/#
@Entity //-$0/ <)"$1 $/1'$$/ /' )( ?*- )( *'0()<
/# @ColumnInfo ))*//$*)/*/# +-*+ -/4<2$/# name +-*+ -/4/#/+-*1$ .4*0-
 .$- )( !*-/# *'0()>
THE DAO OF ENTITIES
25
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport android.database.Cursorandroid.database.Cursor
importimport androidx.room.*androidx.room.*
@Entity(tableName = "customColumn")
classclass CustomColumnNameEntityCustomColumnNameEntity(
@PrimaryKey
valval id: StringString,
valval title: StringString,
@ColumnInfo(name = "words") valval text: StringString? = nullnull,
valval version: IntInt = 1
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM customColumn")
funfun loadAll(): ListList<CustomColumnNameEntityCustomColumnNameEntity>
@Query("SELECT * FROM customColumn where id = :id")
funfun findByPrimaryKey(id: StringString): CustomColumnNameEntityCustomColumnNameEntity
@Query("SELECT * FROM customColumn where id IN (:ids)")
funfun findByPrimaryKeys(varargvararg ids: StringString): ListList<CustomColumnNameEntityCustomColumnNameEntity>
@Query("SELECT * FROM customColumn where id IN (:ids)")
funfun findByPrimaryKeys(ids: ListList<StringString>): ListList<CustomColumnNameEntityCustomColumnNameEntity>
@Query("SELECT * FROM customColumn LIMIT :limit")
funfun loadFirst(limit: IntInt): ListList<CustomColumnNameEntityCustomColumnNameEntity>
@Query("SELECT * FROM customColumn")
funfun loadCursor(): CursorCursor
@Insert
funfun insert(entity: CustomColumnNameEntityCustomColumnNameEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
 - <2 #)" /# text +-*+ -/4D.*'0()/* words<'*)"2$/#.+ $!4$)"/#
/' )( ?# 2$''- : //#/#)" >
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS customColumn (id TEXT NOTNOT NULLNULL, title TEXT NOTNOT NULLNULL, words
TEXT, versionversion INTEGER NOTNOT NULLNULL, PRIMARYPRIMARY KEYKEY(id))
@ 1 )/#*0"#2 ./$''- ! -/*/# +-*+ -/44$/.- "0'-*/'$))( >
THE DAO OF ENTITIES
26
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport org.junit.Assert.*org.junit.Assert.*
importimport java.util.*java.util.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass CustomColumnNameEntityTestCustomColumnNameEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.customColumn()
@Test
funfun customColumnPersists() {
assertThat(underTest.loadAll(), isEmpty)
valval original = CustomColumnNameEntityCustomColumnNameEntity(
id = UUIDUUID.randomUUID().toString(),
title = "This space available for rent",
text = "This will be stored as words. Well, it will be stored in a column named 'words'."
)
underTest.insert(original)
valval retrieved = underTest.findByPrimaryKey(original.id)
assertThat(retrieved.id, equalTo(original.id))
assertThat(retrieved.title, equalTo(original.title))
assertThat(retrieved.text, equalTo(original.text))
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4 ./?&/K
*/ </#*0"#</#/()4*!/# ))*//$*)//-$0/ ./#/**(0. .- ! -/*
*'0())( .<)*/+-*+ -/4)( .?*- 3(+' <.0++*. /#/$)./ *!0.$)" * /*
$)$/ /#/*0-,0 -$ ..#*0'- /0-)''*'0().<2 '$.//# *) ./#/2 2)/@
)2 0. +-*+ -/4)( .>
@Query("SELECT id, title, text, version FROM customColumn")
funfun loadAll(): ListList<CustomColumnNameEntityCustomColumnNameEntity>
THE DAO OF ENTITIES
27
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
)-*$/0$*2$'' 0)#++42$/#4*0>
Figure 1: Android Studio, Showing Syntax Error
)<$!4*0/-4$")*-$)")-*$/0$*)0$'$)"/# +-*% /)424<4*02$''
" /0$' --*->
error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing
database (no such column: text)
public abstract
java.util.List<com.commonsware.room.misc.CustomColumnNameEntity> loadAll();
*0) /*0. /# *'0())( $)./ >
@Query("SELECT id, title, words, version FROM customColumn")
funfun loadAll(): ListList<CustomColumnNameEntityCustomColumnNameEntity>
'.*)*/ /#/$)" @ColumnInfo /* @Transient +-*+ -/4( )./#//#$.
+-*+ -/42$'' $)'0 2# )- /$)"/# /' ./-0/0- < .+$/ /# @Transient
))*//$*)?4 !0'/< @Transient +-*+ -/$ .- $")*- <0/$)" @ColumnInfo
$)$/ ./#/4*02)//#/ !0'/ #1$*-/* *1 --$ )?
Other @ColumnInfo Options
 4*).+ $!4$)"/# *'0())( /*0. <4*0)*)8"0- */# -*+/$*).*)
@ColumnInfo ))*//$*)? .20.$)" index = true -'$ -/*)$) 3/*
*'0()<0/2 #1 *+/$*). 4*)/#/.2 ''?
Collation
*0).+ $!4 collate +-*+ -/4/*$)$/ /# *''/$*). ,0 ) /*++'4/*
/#$.*'0()? - <E*''/$*). ,0 ) F$.!)424*!.4$)"E*(+-$.*)
!0)/$*)!*-*(+-$)"/2*./-$)".F?
# - - !*0-*+/$*).>
I BINARY ) UNDEFINED<2#$#-  ,0$1' )/</#  !0'/1'0 <)$)$/
/#/. $.. ).$/$1
I NOCASE<2#$#$)$/ ./#/. $.)*/. ).$/$1 J(*- 0-/ '4</#//#
THE DAO OF ENTITIES
28
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
QU)"'$.#' // -.- *)1 -/ /*0++ -. K
I RTRIM<2#$#$)$/ ./#//-$'$)".+ ..#*0' $")*- *). A
. ).$/$1 *''/$*)
# - $.)*!0''A ,0$1' )/*! NOCASE $)$/ ?
Type Affinity
*-(''4<**(2$'' / -($) /# /4+ /*0. *)/# *'0()$)$/ . 0+*)
/# /4+ *!/# +-*+ -/4J ?"?< Int +-*+ -/$ .- / INTEGER *'0().K?!<!*-.*(
- .*)<4*02$.#/*/-4/**1 --$ /#$. #1$*-<4*0)0. /# typeAffinity
+-*+ -/4*) @ColumnInfo /*.+ $!4.*( */# -/4+ /*0. ?
Default Values
@ColumnInfo '.*#. defaultValue +-*+ -/4?.4*0($"#/"0 ..!-*(/# )( <
$/+-*1$ . !0'/1'0 !*-/# *'0()$)/# /'  8)$/$*)?
*2 1 -<E*0/*!/# *3F<$/(4 ' ..0. !0'/#)4*0/#$)&?!4*0 @Insert )
)/$/4</# 1'0 !*-/#$.*'0()!-*(/#  )/$/42$'' 0. <)*//#  !0'/1'0 ?
2$'' 3+'*- defaultValue<). )-$*.2# - $/$.0. !0'< '/ -$)/# **&?
DAOs and Queries
) +*+0'-/#$)"/**2$/#/. $./*" //*0/*!$/?*-/#/<2 
@Query !0)/$*).*)*0-?
#*. *)*/#1 /*  .+ $''4*(+' 3?# loadAll() !0)/$*).$)/# .(+' .
.#*2)$)/#$.#+/ --  '$"#/!0''4.$(+' >
@Query("SELECT * FROM customColumn")
funfun loadAll(): ListList<CustomColumnNameEntityCustomColumnNameEntity>
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
*2 1 -<,0 -$ .2$/#$/ )" /- (-&'4*(+'$/ ?**(/-$ ./*
.0++*-/'*/*!/# ./)-.4)/3<0/**(.$/.*2)*(+' 3$/4<$)
/ -(.*!/-4$)"/* $+# -#*2/*$)/ -+- /4*0- @Query !0)/$*)D.-"0( )/.)
- /0-)/4+ ?
THE DAO OF ENTITIES
29
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Adding Parameters
.2 .22$/#!0)/$*).'$& findByPrimaryKey()<4*0)(+!0)/$*)
-"0( )/./*,0 -4+-( / -.40.$)" : .4)/3?0/ :  !*- /# -"0( )/)(
)$/.1'0 2$'' $)% / $)/*/# ,0 -4>
@Query("SELECT * FROM customColumn where id = :id")
funfun findByPrimaryKey(id: StringString): CustomColumnNameEntityCustomColumnNameEntity
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
WHERE Clause
-$)$+''4<4*0-!0)/$*)-"0( )/.2$'' $)% / $)/*4*0- WHERE '0. <.0#.
$)/# *1  3(+' ?
*/ /#/**(#..+ $'.0++*-/!*- IN $) WHERE '0. <2# - 4*0),0 -4
0.$)" vararg *- List +-( / ->
@Query("SELECT * FROM customColumn where id IN (:ids)")
funfun findByPrimaryKeys(varargvararg ids: StringString): ListList<CustomColumnNameEntityCustomColumnNameEntity>
@Query("SELECT * FROM customColumn where id IN (:ids)")
funfun findByPrimaryKeys(ids: ListList<StringString>): ListList<CustomColumnNameEntityCustomColumnNameEntity>
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
 - </# IN (:ids) .4)/32$''  3+) 4**(/*$)'0 ''*!/#
1'0 ./#/4*0.0++'4$)/# -"0( )/?)/#$.. <4*02*0'- /-$ 1 ''*!/#
)/$/$ .(/#$)")4*!/#*. +-$(-4& 41'0 .?
Other Clauses
# - 1 -$/ ''*2. ? +' #*' -.<**(.#*0'''*2!0)/$*)-"0( )/./*
 0. $)./ ?
*<!*- 3(+' <4*0)+-( / -$5  LIMIT '0. >
@Query("SELECT * FROM customColumn LIMIT :limit")
funfun loadFirst(limit: IntInt): ListList<CustomColumnNameEntityCustomColumnNameEntity>
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
THE DAO OF ENTITIES
30
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
What You Can Return
#1 . )/#/ @Query )- /0-).$)"'  )/$/4J ?"?</# .$)"' A
findByPrimaryKey() !0)/$*).K*-*'' /$*)*! )/$/$ .J ?"?< loadAll() - /0-)$)"
List *! )/$/$ .K?
#$' /#*. - .$(+' <**(*7 -.!$-$/(*- : 3$$'$/4/#)/#/?
Returning CursorCursor
)$/$*)/*- /0-)$)".$)"' *% /.*-*'' /$*).*!*% /.<**( @Query )
- /0-)"***'A!.#$*)  Cursor>
@Query("SELECT * FROM customColumn")
funfun loadCursor(): CursorCursor
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
#$.$.+-/$0'-'40. !0'$!4*0- ($"-/$)"' "4* /#/0. . CursorAdapter
*-*/# - CursorA.+ $8'.. .?$($'-'4<$!4*0- '**&$)"/* 3+*. +-/*!
**(A 8) /. 1$ ContentProvider<$/(4 (*- *)1 )$ )/!*-4*0
/*" /4*0-- .0'/.$)/# !*-(*! Cursor<.*/#/4*0)%0./- /0-)/#/!-*(/#
+-*1$ -D. query() !0)/$*)?
*/ /#/<.2$/#" //$)" Cursor !-*( SQLiteDatabase<4*0- - .+*).$' !*-
'*.$)"/# Cursor 2# )4*0- *) 2$/#$/?
Non-Entity Results
*-.('' )/$/$ .<'$& /# *) ..#*2).*!-$)/#$.#+/ -<0.0''42 2$''- /-$ 1
''*'0().$)/# ,0 -4?*2 1 -</# - '-0' $.>/# *- - /0-)*% /*!/#
@Query !0)/$*)(0./ .*( /#$)"/#/**(&)*2.#*2/*8''$)!-*(/#
*'0()./#/4*0- ,0 ./?
*-2$ -/' .2$/#()4*'0().</#$.$.$(+*-/)/?*- 3(+' <+ -#+.!*-
RecyclerView<4*0*)'4) *0+' *!*'0().<0/!*-'' )/$/$ .$)/# /' ?)
/#/. <$/($"#/ )$ /**)'4- /-$ 1 /#*. .+ $8*'0().?*0#1 /2*24.
/**/#/>
P? 1 4*0- @Entity .0++*-/*)'4.0. /*!*'0().<''*2$)"/# - .//*
null *-*/# -2$. /-&$)"/# !//#/2 *)'4- /-$ 1 .0. /*!
THE DAO OF ENTITIES
31
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*'0().!-*(/# /'
Q?  /0-).*( /#$)"*/# -/#)/#  )/$/4/#/4*0#1 ..*$/ 2$/#/#$.
/'
!4*0'**&/4*0- @DaoA))*// $)/ -! <4*02$'')*/$ /#/2#$' !0)/$*).
($"#/- ! -/* )/$/$ .<$/.))*//$*).*)*/?#/$. 0. /# $..*( 2#/
$) + ) )/*!/#  )/$/$ .?#  )/$/$ . .-$ /# /' <0//# $.)*/
'$($/ /*0.$)"/#*.  )/$/$ .?*'*)"./# )!0'8''/# *)/-/./$+0'/ 
4/# </# !0)/$*)-"0( )/.<)/# !0)/$*)- /0-)/4+ <**($.+ -! /'4
#++4?
*- 3(+' <.0++*. /#/4*02 - (&$)")++./*- '$ )/?#$' ()4
 1 '*+ -./#$)&/#//# '4/*- $./# *)'4++./*- '$ )/</# - - '*/.*!
'/ -)/$1 .<.0#. A-*$<2#$#.+ $'$5 .$)*+ ).*0- ++.?
)4*0-/(* '<4*02$'') .*( .*-/*!**( )/$/4- +- . )/$)"++.$)/#
./*- /'*"?#/(4$)1*'1 '*/*!/<2# - 4*0($"#/) ''*!/#//
!*-/# ++E$.+'4F.- )$)4*0-++?*2 1 -<2# )4*0- +- . )/$)"'$./*!
++.H/# 2#*' /'*"<.*( / "*-4*!++.<*-. -#- .0'/.H4*0(4) 
%0./.0. /*!/#//?*-/#/<4*0)/& 1)/" *!**(D.,0 -4: 3$$'$/4
) 8) E'$./(* 'F'../#/*)/$)./# .0. /*!//#/4*0-'$./.) >
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
@Entity(tableName = "apps")
data classdata class AppEntityAppEntity(
@PrimaryKey
valval applicationId: StringString,
valval displayName: StringString,
valval shortDescription: StringString,
valval fullDescription: StringString,
valval latestVersionName: StringString,
valval lastUpdated: LongLong,
valval iconUrl: StringString,
valval packageUrl: StringString,
valval donationUrl: StringString
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM apps")
funfun loadAll(): ListList<AppEntityAppEntity>
@Query("SELECT applicationId, displayName, shortDescription, iconUrl FROM apps")
funfun loadListModels(): ListList<AppListModelAppListModel>
@Insert
funfun insert(entity: AppEntityAppEntity)
}
THE DAO OF ENTITIES
32
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
}
data classdata class AppListModelAppListModel(
valval applicationId: StringString,
valval displayName: StringString,
valval shortDescription: StringString,
valval iconUrl: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G++)/$/4?&/K
 - < AppEntity $/. '!$.0)- (-&' ?*2 1 -<2 '.* 8) ) AppListModel
'..<2$/#.0. /*!/# AppEntity +-*+ -/$ .? AppEntity.Store )*/*)'4 8) .
loadAll() !0)/$*)/#/- /0-). )/$/$ .<0/$/#. loadListModels() !0)/$*)
/#/- /0-). AppListModel *% /.? loadListModels() #. @Query ))*//$*)/#/
- /0-).*)'4/# *'0().)  4 AppListModel<)**(2$''#++$'4+*0-/#/
/$)/* AppListModel *% /.!*-0.?# *)'4*)) /$*) /2 ) AppEntity )
AppListModel $./#/*0- loadListModels() ,0 -4,0 -$ ./# apps /' ?
*2<++* /#/$.+'4.'$./.<*-*/# -2$. *)'4) ./#$..0. /*!/# *1 -''
)/$/4/<)2*-&2$/#/# E'$./(* '.F$)./ >
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.hasSizecom.natpryce.hamkrest.hasSize
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass AppEntityTestAppEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.apps()
@Test
funfun queryDisplayModels() {
assertThat(underTest.loadAll(), isEmpty)
assertThat(underTest.loadListModels(), isEmpty)
valval fdroid = AppEntityAppEntity(
applicationId = "org.fdroid.fdroid",
displayName = "F-Droid",
shortDescription = "An independent app store featuring open source Android apps",
fullDescription = "F-Droid is an installable catalogue of FOSS (Free and Open Source Software)
applications for the Android platform. The client makes it easy to browse, install, and keep track of
THE DAO OF ENTITIES
33
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
updates on your device. Visit https://f-droid.org to learn more!",
lastUpdated = 1566652015000,
latestVersionName = "1.7.1",
donationUrl = "https://flattr.com/thing/343053/F-Droid-Repository",
packageUrl = "https://f-droid.org/FDroid.apk",
iconUrl = "https://gitlab.com/fdroid/fdroidclient/raw/master/app/src/main/res/drawable-hdpi/
ic_launcher.png?inline=true"
)
underTest.insert(fdroid)
underTest.loadAll().let {
assertThat(it, hasSize(equalTo(1)))
assert(it[0] == fdroid)
}
underTest.loadListModels().let {
assertThat(it, hasSize(equalTo(1)))
valval model = it[0]
assertThat(model.applicationId, equalTo(fdroid.applicationId))
assertThat(model.displayName, equalTo(fdroid.displayName))
assertThat(model.shortDescription, equalTo(fdroid.shortDescription))
assertThat(model.iconUrl, equalTo(fdroid.iconUrl))
}
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G++)/$/4 ./?&/K
*/ /#/ @ColumnInfo ))*//$*).) 0. *))4'..<)*/%0./ )/$/$ .?
- ,0 )/'4<$!4*00. @ColumnInfo *)+-*+ -/4$)) )/$/4<4*02$''2$)0+0.$)"
/#/.( @ColumnInfo *)/# *-- .+*)$)"+-*+ -/4$))4.*-/*!E'$./(* 'FA
./4' *% /<.*/#//# +-*+ -/4)( .'$) 0+2$/#/# *'0()$)/# /' ?
Reactive Return Types
''*!*0-!0)/$*).#1 - /0-) 1'0 .$- /'4<2# /# -/#*. 1'0 .-
)/$/$ .<'$./.*! )/$/$ .<*-.*( /#$)" '. J ?"?<'$./*!.*( E/0+' F*% /.K?
#*. !0)/$*).- .4)#-*)*0.?# ,0 -42$'' + -!*-( 2# )2 ''/#
!0)/$*)<)2 " //# - .0'/.*!/# ,0 -4- /0-) /*0.?
#/$.1 -4.$(+' ?/$.'.*1 -4))*4$)"<.$/( )./#/2 #1 /* '2$/#
&"-*0)/#- .*0-. '1 .? *)*/2)//* ''$)"/# . !0)/$*).*)
/# ($)++'$/$*)/#- <./. G) .'*2?.- .0'/<2 2$''
!*- /*0. .*( /#$)" '. <.0#.1 Executor</* ' /*''/# !0)/$*).
!-*(&"-*0)/#- ?
THE DAO OF ENTITIES
34
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*2 1 -<$!4*0'**&/'*/*!**(.(+' * <4*02$''. /#//#
@QueryA))*// !0)/$*).*!/ )2-+/# - /0-)1'0 .$)@.*( /#$)" '. >
I LiveData
I Flow
I Single
I Observable
I Flowable
I ).**)
# . - '.. .!-*(- /$1 !-( 2*-&.? LiveData $.+-/*!/# )-*$ /+&<
Flow $.!-*(*/'$)*-*0/$) .<)/# */# -.- !-*(31?# . !-( 2*-&.
-  .$") /*# '+.$(+'$!4/#- $)"?# 4#$ /# *(+' 3$/4*!*$)"/#
/0'/. G*)&"-*0)/#- 2#$' " //$)"/# - .0'/./*4*0*)/#
($)++'$/$*)/#- J*-.*( */# -/#- *!$)/ - .//*4*0K?
2$'' 3+'*- /# . *+/$*).<)/#- .2$/#**($)" ) -'< $))0+*($)"
#+/ -?
Aggregate Functions
.0++*-/.""- "/ !0)/$*).<'$& COUNT ) SUM?!4*0$)'0 /# . $)
,0 -4<4*0" //#*. '0'/ 1'0 .&<$)./ *!J*-+ -#+.$)$/$*)/*K
/0'/!-*(/' ?
**('.*.0++*-/.""- "/ !0)/$*).?*2 1 -<4 8)$/$*)</# - $.)* )/$/4
2#*. +-*+ -/$ .- *0)/.*-.0(.? #1 /2**+/$*).!*-" //$)"*0-- .0'/.
&!*-/# . '0'/$*).>
I !2 ) %0./.$)"' 1'0 <2 )#1  @QueryA))*// !0)/$*)
- /0-)) Int< Long<*-*/# -.$/4+ /#/- +- . )/./# - .0'/!-*(/#
""- "/ !0)/$*)
I /# -2$. <2 0. 1-$/$*)*)/# ++-*#!-*(/# +-  $)". /$*)<
2# - 2 - /  data class *-.$($'-./-0/0- /#/**()0. /*
- /0-)*0-- .0'/.
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
importimport kotlin.random.Randomkotlin.random.Random
data classdata class CountAndSumResultCountAndSumResult(valval count: IntInt, valval sum: LongLong)
THE DAO OF ENTITIES
35
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@Entity(tableName = "aggregate")
classclass AggregateEntityAggregateEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong = 0,
valval value: LongLong = RandomRandom.nextLong(1000000)
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM aggregate")
funfun loadAll(): ListList<AggregateEntityAggregateEntity>
@Query("SELECT COUNT(*) FROM aggregate")
funfun count(): IntInt
@Query("SELECT COUNT(*) as count, SUM(value) as sum FROM aggregate")
funfun countAndSum(): CountAndSumResultCountAndSumResult
@Insert
funfun insert(entities: ListList<AggregateEntityAggregateEntity>)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G""- "/ )/$/4?&/K
 - <*0- )/$/4%0./#.)0/*A" ) -/ +-$(-4& 4<+'0..*( */# --)*('4A
" ) -/ Long 1'0 J0))$)"'4./*- $) value +-*+ -/4K?)$/...*$/ 
AggregateEntity.Store<$)$/$*)/**0-./)- loadAll() !0)/$*)))
insert() /#/ +/. List *! )/$/$ .<2 #1 >
I count()<2#$#- /0-).) Int - +- . )/$)"/# *0)/*!-*2.$)/# /'
I countAndSum()<2#$#- /0-)./# *0)/*!-*2.)/# .0(*!/# 1'0 .<
$)/# !*-(*! CountAndSumResult?
*/ /#//# !*- countAndSum() 0. ./# AS *+ -/*-/*+0/.+ $8)( *)
/# - /0-) 1'0 .?#*. ) /*(+/*/# +-*+ -/4)( .$)4*0-- .0'//4+ ?
)/# )0. /#*. !0)/$*).%0./'$& )4*/# - @Dao !0)/$*).?)<$)/# .
*! countAndSum()<2 )0. */'$)D. ./-0/0-$)" '-/$*)./*1*$#1$)"/*
!0..2$/# CountAndSumResult>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
THE DAO OF ENTITIES
36
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.greaterThancom.natpryce.hamkrest.greaterThan
importimport com.natpryce.hamkrest.hasSizecom.natpryce.hamkrest.hasSize
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport java.util.*java.util.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass AggregateEntityTestAggregateEntityTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.aggregate()
@Test
funfun aggregateFunctions() {
assertThat(underTest.loadAll(), isEmpty)
underTest.insert(ListList(100) { AggregateEntityAggregateEntity() })
assertThat(underTest.count(), equalTo(100))
valval (count, sum) = underTest.countAndSum()
assertThat(count, equalTo(100))
assertThat(sum, greaterThan(0L))
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G""- "/ )/$/4 ./?&/K
Dynamic Queries
*( /$( .<4*0*)*/&)*2/# ,0 -4/*(+$' /$( ?
) . )-$*!*-/#$.$.2# )4*02)//* 3+*. **(A()" /. 1$
ContentProvider /*/#$-A+-/4++.?*0*0'*0( )//#/4*0.0++*-/
'$($/ . /*!*+/$*).$)4*0-+-*1$ -D. query() !0)/$*)<*) ./#/4*0)(+/*
@Query !0)/$*).*)4*0-?'/ -)/$1 '4<4*0*0'" ) -/ .// ( )/
THE DAO OF ENTITIES
37
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
0.$)" SQLiteQueryBuilder /#/.0++*-/.2#/4*0-/' *7 -.<0//# )4*0) 
/*.*( #*2 3 0/ /#/.// ( )/)" / Cursor &?
*0#1 ! 2*+/$*).!*-#)'$)"/#$..*-/*!.$/0/$*)?
query()
RoomDatabase #. query() !0)/$*)/#/$.)'*"*0./* rawQuery() *)
SQLiteDatabase?..$//# .// ( )/)) Object --4*!+*.$/$*)
+-( / -.<) RoomDatabase 2$''"$1 4*0 Cursor &?
#  ) 8/$./#//#$.$.,0$&) .4?# *2).$ $./#/4*02$)0+2$/#
Cursor<2#$#$.' ..*)1 )$ )//#)/# (* '*% /./#/4*0" /&!-*(
@Query !0)/$*).*)4*0- @Dao?
@RawQuery
)*/# -*+/$*)$. @RawQuery?$& @Query</#$.$.)))*//$*)/#/4*0)/*
!0)/$*)*)4*0- @Dao?)<'$& @Query<4*0)#1 /#/!0)/$*)- /0-)
$)./) .*!) @Entity *-*/# -?
*2 1 -<-/# -/#).0++'4$)"83 .// ( )/$)/# ))*//$*)<4*0
+-*1$  SupportSQLiteQuery *% /.+-( / -/*/# @RawQuery !0)/$*)>
@RawQuery
funfun _findMeSomething(query: SupportSQLiteQuerySupportSQLiteQuery): ListList<FooFoo>
SupportSQLiteQuery *( .!-*( /# .0++*-//. <2#$#$.#*2**(
$)/ -/.2$/#4*0-$/ /. ?*-/0)/ '4<!*-/# +0-+*. .*!0.$)"
@RawQuery</# *)'4/#$)"/#/4*0) !-*(/#/$. SimpleSQLiteQuery?/.
*)./-0/*-/& ./# .( /2*+-( / -..* . rawQuery() *)
SQLiteDatabase>
I # .// ( )//* 3 0/ <)
I ) Object --4*!1'0 ./*0. /*- +' +*.$/$*)'+' #*' -.
*<4*0)2-+4*0-,0 -4)+' #*' -1'0 .$) SimpleSQLiteQuery<+..
/#//*4*0- @RawQueryA))*// !0)/$*)<)**(2$''/& - *!/# - ./?
THE DAO OF ENTITIES
38
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Other DAO Operations
*" //*0/*!/. <" ) -''4$/$.0. !0'/*+0//$)/*$/? #1 . )
.$ @Insert< @Update<) @Delete !0)/$*).*) NoteStore>
packagepackage com.commonsware.room.notescom.commonsware.room.notes
importimport androidx.room.*androidx.room.*
@Dao
interfaceinterface NoteStoreNoteStore {
@Query("SELECT * FROM notes")
funfun loadAll(): ListList<NoteEntityNoteEntity>
@Insert
funfun insert(note: NoteEntityNoteEntity)
@Update
funfun update(note: NoteEntityNoteEntity)
@Delete
funfun delete(varargvararg notes: NoteEntityNoteEntity)
}
J!-*( */ .$.G.-G($)G%1G*(G*((*).2- G-**(G)*/ .G*/ /*- ?&/K
 ) -''4.+ &$)"</# . . )-$*.- .$(+' -/#) @Query?# @Insert< @Update<
) @Delete . /0+.$(+' !0)/$*).!*-$). -/$)"<0+/$)"<*- ' /$)" )/$/$ .
+.. /*/# $-!0)/$*).@)/#/$.+- //4(0#$/?*2 1 -</# - - ! 2
$/$*)'*).$ -/$*)./#/2 .#*0' 3+'*- ?
Parameters
@Insert< @Update<) @Delete 2*-&2$/# )/$/$ .?)/# *1 * < insert() )
update() #/& .$)"'  )/$/4? delete() /& . vararg *! )/$/$ .<.*4*0)
+..*) *-. 1 -'.4*0. 8/?
*0)'.*#1  List *! )/$/$ .<.2 .2$)/# insert() !0)/$*)$)
AggregateEntity>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
importimport kotlin.random.Randomkotlin.random.Random
THE DAO OF ENTITIES
39
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
data classdata class CountAndSumResultCountAndSumResult(valval count: IntInt, valval sum: LongLong)
@Entity(tableName = "aggregate")
classclass AggregateEntityAggregateEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong = 0,
valval value: LongLong = RandomRandom.nextLong(1000000)
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM aggregate")
funfun loadAll(): ListList<AggregateEntityAggregateEntity>
@Query("SELECT COUNT(*) FROM aggregate")
funfun count(): IntInt
@Query("SELECT COUNT(*) as count, SUM(value) as sum FROM aggregate")
funfun countAndSum(): CountAndSumResultCountAndSumResult
@Insert
funfun insert(entities: ListList<AggregateEntityAggregateEntity>)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G""- "/ )/$/4?&/K
Return Values
- ,0 )/'4<4*0%0./#1 /# . !0)/$*).- /0-))*/#$)"J/ #)$''4</# 4- /0-)
Unit</#*0"#2 )-*+/#/$)*/'$)K?
*2 1 ->
I *- @Update ) @Delete<4*0)#1 /# (- /0-)) Int<2#$#2$''
/# )0( -*!-*2.7 / 4/# 0+/ *- ' / *+ -/$*)
I *-) @Insert !0)/$*) +/$)".$)"'  )/$/4<4*0)#1 $/- /0-)
Long 2#$#2$'' /# ROWID *!/#  )/$/4J)<$!4*0- 0.$)")0/*A
$)- ( )/ int .4*0-+-$(-4& 4</#$.2$'''.* /#/& 4K
I *-) @Insert !0)/$*) +/$)"(0'/$+'  )/$/$ .<4*0)#1 $/- /0-)
LongArray *- List *! Long 1'0 .< $)"/# *-- .+*)$)" ROWID 1'0 .!*-
/#*. $). -/  )/$/$ .
THE DAO OF ENTITIES
40
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Conflict Resolution
@Insert ) @Update .0++*-/)*+/$*)' onConflict +-*+ -/4?#$.(+./*
$/ D. ON CONFLICT '0. )$)$/ .2#/.#*0'#++ )$!/# - $. $/# -
0)$,0 ) ..1$*'/$*)J ?"?<0+'$/ +-$(-4& 4.K*- NOT NULL 1$*'/$*)2# )/#
$). -/*-0+/ .#*0'*0-?
# 1'0 *! onConflict $.) OnConflictStrategy )0(>
Value Meaning
OnConflictStrategy.ABORT
) './#$..// ( )/0/+- . -1 .+-$*-
- .0'/.$)/# /-)./$*))& +./#
/-)./$*)'$1
OnConflictStrategy.FAIL
$& ABORT<0/ +/.+-$*-#)" .4/#$.
.+ $8.// ( )/J ?"?<$!2 !$'*)/# TO/#
-*2/* 0+/ <& +/# #)" ./*/#
+-  $)"SXK
OnConflictStrategy.IGNORE
$& FAIL<0/*)/$)0 .+-* ..$)"/#$.
.// ( )/J ?"?<$!2 !$'*)/# TO/#-*2*0/*!
POO<& +/# #)" ./*/# */# -XXK
OnConflictStrategy.REPLACE
*-0)$,0 ) ..1$*'/$*).< ' / .*/# --*2.
/#/2*0'0. /# 1$*'/$*) !*-  3 0/$)"
/#$..// ( )/
OnConflictStrategy.ROLLBACK *''.&/# 0-- )//-)./$*)
#  !0'/./-/ "4!*- @Insert ) @Update $. ABORT?
2$'' 3+'*- /# . *):$/./-/ "$ .$)"- / - /$' (0#'/ -$)/# **&?
Other Operations
# +-$(-4+-*' (2$/# @Insert< @Update<) @Delete $./#//# 4)  )/$/$ .?
)+-/</#/$..*/# !0)/$*)&)*2.2#//' /*2*-&"$)./?
*-)4/#$)" '. <0. @Query? @Query )*/*)'42*-&.2$/#*+ -/$*)./#/- /0-)
THE DAO OF ENTITIES
41
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
- .0'/. /.<0/2$/# any /#/4*02$.#/* 3 0/ < 1 )$!/#/* .)*/
- /0-)- .0'/. /?
*<!*- 3(+' <4*0*0'#1 >
@Query("DELETE FROM aliens")
funfun nukeFromOrbit()
// it's the only way to be sure
@*- INSERT INTO ... SELECT FROM ... .4)/3<*-+- //4(0#)4*/# -
*($)/$*)/#/))*/ .0++*-/ $- /'44 @Insert< @Update<) @Delete
))*//$*).?
*).$ - @Insert< @Update<) @Delete /* E*)1 )$ ) ))*//$*).F!*- )/$/4A
. *+ -/$*).<2# - @Query $./# &*) !*-4*0-!0)/$*).?
Transactions and Room
4 !0'/<$/ /- /. #$)$1$0'.// ( )/.)$)$1$0'/-)./$*)?
*/#  3/ )//#/**(2$).0+" ) -/$)"(0'/$+' .// ( )/.$)- .+*).
/**0-))*//$*).<$/$.**(D.- .+*).$$'$/4/*2-+/#*. .// ( )/.$).0$/'
/-)./$*)?
*2 1 -<.*( /$( .<4*0#1 0.$) ..'*"$/#/- ,0$- ./-)./$*)<!*-
*+ -/$*)./#/- ,0$- (0'/$+' !0)/$*).?*- 3(+' <+ -.$./$)")$)1*$
($"#/$)1*'1 $). -/$)") Invoice )''*!$/. InvoiceLineItem *% /.<)/#/
($"#/- ,0$- (*- /#)*) !0)/$*)/*#$ 1 ?
**(*7 -./2*24.*!. //$)"0+++A 8) /-)./$*).>/# @Transaction
))*//$*)).*( !0)/$*).*) RoomDatabase?
Using @Transaction
*0-)#1 *) *-(*- !0)/$*)./#/#1 /# @Transaction ))*//$*)?
#/ 1 - @TransactionA))*// !0)/$*)* .$.2-++ $)$/
/-)./$*)?# /-)./$*)2$'' *(($// $!/# @TransactionA))*//
!0)/$*)* .)*//#-*2) 3 +/$*)?!$/* .</# /-)./$*)2$'' -*'' &?
# - - /2*+' ./*++'4 @Transaction>0./*( open !0)/$*).*)) abstract
'..<*-*) @Query !0)/$*).?
THE DAO OF ENTITIES
42
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Custom Functions
 - </# $ $./#/4*0- @TransactionA))*// !0)/$*)2*0'(& (0'/$+'
''./**/# -!0)/$*).J ?"?<*) .2$/# @Insert *- @Query ))*//$*).K<.*
/#//# 2*-&+ -!*-( $)/#*. */# -!0)/$*).E.0 *-!$'.2#*' F?
$1 )*0-8/$/$*0. Invoice 3(+' <2 ($"#/#1 .*( /#$)"'$& /#$.>
@Dao
abstractabstract classclass InvoiceStoreInvoiceStore {
@Insert
funfun _insert(invoice: InvoiceInvoice)
@Insert
funfun _insertItems(lineItems: ListList<InvoiceLineItemInvoiceLineItem>)
@Transaction
openopen funfun insert(invoice: InvoiceInvoice) {
_insert(invoice)
_insertItems(invoice.getLineItems())
}
}
 - <2 ./$''0. ) insert() !0)/$*)/*$). -/) Invoice<0/2 0. /#//*2-+
/2*''./*$). -//# Invoice ( //)$). -//# InvoiceLineItem
*% /.?
*/ /#//# !0)/$*)2$/# @Transaction ) ./* open?**(2$''" ) -/ 
*)- / $(+' ( )//$*)*!4*0-< $/# - 3/ )$)"4*0-./-/'..*-
$(+' ( )/$)"4*0-$)/ -! ?*(& @Transaction 2*-&<**(* A" ) -/ .)
*1 --$$)"!0)/$*)/#/2-+.''/*4*0-$(+' ( )//$*)$)/-)./$*)?
*2 1 -<$)*/'$)<*)- / !0)/$*).))*/ *1 --$ )2$/#*0//# open
& 42*-? 1$)"/#/& 42*-*7 (4- .0'/$)./-)" *(+$'  --*-( .." .?
On @Query Functions
/(4. (*/*#1 /*.+ $8''4- ,0 .//-)./$*)*) @QueryA))*// 
!0)/$*)?!/ -''</#  !0'/ #1$*-*!$/ $./*#1  #$)$1$0'
.// ( )/ $)$/.*2)/-)./$*)?
*2 1 -</# - - /2*. )-$*.'' *0/$) /# *0( )//$*) 2# -
@Transaction 2*0' "**$ ?) $./$ /* @Relation<2#$#2 2$''*1 -
THE DAO OF ENTITIES
43
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
'/ -$)/# **&?
# */# -$./$ /*'$//' A&)*2)$..0 2$/#)-*$D.$/ .0++*-/>/#$)"." /
2 $-2# )/# - .0'/. /*!,0 -4 3 .P?)/#/. <0.$)"/# - "0'-
)-*$ SQLiteDatabase </# Cursor /#/4*0" /&* .)*/*)/$)/# !0''
- .0'/. /?)./ <$/*)/$).E2$)*2F*!- .0'/.<)$!4*0+*.$/$*)/# Cursor
!/ -/#/2$)*2</# ,0 -4$.- A 3 0/ /*'*$)/# ) 3/2$)*2?#$.)' 
/*$)*).$./ )$ .<$!/# /. $.#)" $) /2 )/#*. /2*/.
- ,0 ././*+*+0'/ /# 2$)*2?**(<4 !0'/<2$'''*/#  )/$- - .0'/. /
$)/*4*0- )/$/$ .<,0$&'4(*1$)"/#-*0"#/# 2$)*2..)  <0//# - $../$''
#) /#//. (*$8/$*)*0-.2#$' /#$.$."*$)"*)?.$)"
@Transaction 2*0'# '+ ).0- /#//#$.$.)*/)$..0 <4#1$)"/#  )/$- ,0 -4
H$)'0$)"/-1 -.$)"/# 2$)*2.H*0-$).$ /-)./$*)?
Using RoomDatabase
'/ -)/$1 '4< RoomDatabase *7 -./# .( beginTransaction()<
endTransaction()<) setTransactionSuccessful() !0)/$*)./#/4*0. *)
SQLiteDatabase<).*4*00. /# .( .$'"*-$/#(>
roomDb.beginTransaction()
trytry {
// bunch of DAO operations here
roomDb.setTransactionSuccessful()
}
finallyfinally {
roomDb.endTransaction()
}
# 1)/" /*/#$.++-*#$./#/4*0)+0//# /-)./$*)'*"$.*( 2# -
*/# -/#)/# <$!/#/2*0' (*- *)1 )$ )/*-(& (*- . ). !*-4*0-
+-/$0'-$(+' ( )//$*)?*2 1 -<$/$.$/(*- 2*-&?
THE DAO OF ENTITIES
44
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room and Custom Types
*!-<''*!*0-+-*+ -/$ .#1  ).$+-$($/$1 .J.0#. IntK*- String?# -
$."**- .*)!*-/#/>/#*. - ''/#/**(0) -./).E*0/*!/# *3F?
1 -4/#$)" '. - ,0$- ..*( (*0)/*!..$./) *)*0-+-/?
*( /$( .<+-*+ -/4$)) )/$/42$'' - '/ /*)*/# - )/$/4?#*. -
- '/$*).<)2 2$''*).$ -/#*. $) /# ) 3/#+/ -?
*2 1 -<*/# -/$( .<+-*+ -/4$)) )/$/4* .)*/(+$- /'4/*+-$($/$1 .
) String /4+ .<*-/*)*/# - )/$/4?*- 3(+' >
I #/*2 *2$/#1 Date< Calendar<*- Instant *% /.C*2 2)/
/*./*- /#/.($''$. *).A.$) A/# A)$3A +*#1'0 . LongC*2
2)//*./*- ./-$)"- +- . )//$*)$)./)-!*-(/<!*- .$ -
- $'$/4J//# *./*!$.&.+ )*/# -$..0 .KC
I #/*2 *2$/# Location *% /C - <2 #1 /2*+$  .>'/$/0
)'*)"$/0 ?*2 #1 /2**'0()./#/*($) $)/**) +-*+ -/4C
*2 *)1 -//# Location /*)!-*( String - +- . )//$*)C
I #/*2 *2$/#*'' /$*).*!./-$)".<.0#.'$./.*!/".C
I #/*2 *2$/# )0(.C
).**)?
)/#$.#+/ -<2 2$'' 3+'*- /2*++-*# .!*-#)'$)"/# . /#$)".2$/#*0/
- /$)")*/# - )/$/4'..>/4+ *)1 -/ -.) (  /4+ .?
Type Converters
4+ *)1 -/ -.- +$-*!!0)/$*).<))*// 2$/# @TypeConverter</#/(+
45
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/# /4+ !*-.$)"' /. *'0()/*/4+ !*-*/'$)+-*+ -/4?*<!*- 3(+' <
2 )>
I +) Instant +-*+ -/4/* Long<2#$#)"*$)$/ INTEGER
*'0()
I + Location +-*+ -/4/* String<2#$#)"*$)$/ TEXT *'0()
I +*'' /$*)*! String 1'0 ./*.$)"' String J ?"?<*((A. +-/
1'0 .K<2#$#)"*$)$/ TEXT *'0()
I /?
*2 1 -</4+ *)1 -/ -.*7 -*)'4P>P*)1 -.$*)>.$)"' +-*+ -/4/*)!-*(
.$)"' $/ *'0()?!4*0#1 .$)"' +-*+ -/4/#/.#*0'(+/*. 1 -'
$/ *'0().</# @Embedded ++-*#)#)' /#/<.2 2$''. '/ -$)/#$.
#+/ -?
Setting Up a Type Converter
$-./< 8) */'$)'...*( 2# - ?# )( <+&" <.0+ -'..< /?*)*/
(// -?
 3/<!*- #/4+ /* *)1 -/ <- / /2*!0)/$*)./#/*)1 -/!-*(*) /4+
/*/# */# -?*!*- 3(+' <4*02*0'#1 *) !0)/$*)/#//& .) Instant )
- /0-). Long J ?"?<- /0-)$)"/# ($''$. *).A.$) A/# A)$3A +*#1'0 K<)
*0)/ -+-/!0)/$*)/#//& . Long )- /0-).) Instant?!/# *)1 -/ -
!0)/$*)$.+..  null</# +-*+ -- .0'/$. null?/# -2$. </# *)1 -.$*)$.
2#/ 1 -4*02)/<.*'*)"./# E-*0)/-$+F2*-&.<.*/#//# *0/+0/*!*)
*)1 -/ -!0)/$*)<.0++'$ .$)+0//*/# */# -*)1 -/ -!0)/$*)<- /0-)./#
*-$"$)'1'0 ?
# )< #*!/#*. !0)/$*)." //# @TypeConverter ))*//$*)?# !0)/$*)
)( .*)*/(// -<.*+$&*)1 )/$*)/#/2*-&.!*-4*0?
$)''4<4*0 @TypeConverters ))*//$*)<'$./$)"/#$.))4*/# -/4+
*)1 -/ -'.. .</*@.*( /#$)"?#//# E.*( /#$)"F$.*)/-*'./# .*+ *!
2# - /#//4+ *)1 -/ -) 0. ?
# .$(+' .*'0/$*)$./* @TypeConverters /*/# RoomDatabase<2#$#( ).
/#/)4/#$)"..*$/ 2$/#/#//. )0. /#*. /4+ *)1 -/ -.?*2 1 -<
.*( /$( .<4*0(4#1 .$/0/$*).2# - 4*02)/$7 - )/*)1 -.$*). /2 )
/# .( +$-*!/4+ .<!*-2#/ 1 -- .*)?)/#/. <4*0)+0//#
@TypeConverters ))*//$*).*))--*2 -.*+ .>
ROOM AND CUSTOM TYPES
46
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@TypeConverters@TypeConverters Location Aected Areas
)/$/4'.. ''+-*+ -/$ .$)/#  )/$/4
)/$/4+-*+ -/4 /#/*) +-*+ -/4$)/#  )/$/4
'.. ''!0)/$*).$)/# 
!0)/$*) /#/*) !0)/$*)$)/# <!*-''+-( / -.
!0)/$*)+-( / - /#/*) +-( / -*)/#/*) !0)/$*)
*- 3(+' </# TransmogrifyingEntity 8' $)/# /# MiscSamples (*0' *! /#
**&D.+-$(-4.(+' +-*% / #.)*/*)'4 TransmogrifyingEntity 0/'.*
TypeTransmogrifier '..? /-).(*"-$8 - $.LROA4 -A*'+$  *!1)
/ #)*'*"4/#/)*)1 -/*) /#$)"$)/*)*/# -? TypeTransmogrifier #.. /*!
!0)/$*)./#//0-)*) /4+ $)/*)*/# -H2 2$'' 3($) /#*. !0)/$*).$)
0+*($)". /$*).? TransmogrifyingEntity $/. '!#./# @TypeConverters
))*//$*)<$)$/$)"/#//# /4+ *)1 -/ -.*) TypeTransmogrifier .#*0'
0. !*-/#/ )/$/4>
@Entity(tableName = "transmogrified")
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
data classdata class TransmogrifyingEntityTransmogrifyingEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong = 0,
valval creationTime: InstantInstant = InstantInstant.now(),
valval location: LocationLocation = LocationLocation(nullnull asas StringString?).apply {
latitude = 0.0
longitude = 0.0
},
valval tags: SetSet<StringString> = setOf()
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM transmogrified")
funfun loadAll(): ListList<TransmogrifyingEntityTransmogrifyingEntity>
@Insert
funfun insert(entity: TransmogrifyingEntityTransmogrifyingEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
ROOM AND CUSTOM TYPES
47
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Example: Dates and Times
/4+$'24*!./*-$)"/ G/$( 1'0 $)/. $./*0. /# )0( -*!
($''$. *)..$) /# )$3 +*#J$? ?</# )0( -*!($''$. *)..$) ($)$"#/<P
)0-4PXVOK? Instant #. getEpochMillis() !0)/$*)/#/- /0-)./#$.1'0 ?
# TypeTransmogrifier '..#.+$-*!!0)/$*). .$") /**)1 -/ /2 )
) Instant ) Long>
@TypeConverter
funfun instantToLong(timestamp: InstantInstant?) = timestamp?.toEpochMilli()
@TypeConverter
funfun longToInstant(timestamp: LongLong?) =
timestamp?.let { InstantInstant.ofEpochMilli(it) }
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
##./# @TypeConverter ))*//$*)<.***(&)*2./* 3($) /#*.
!0)/$*).2# )$/#./# ) /**)1 -/ /2 )/4+ .<.0#.8)$)".*(
**(A)/$1 /4+ $)/*2#$#2 )*)1 -/) Instant?
TransmogrifyingEntity #.) Instant +-*+ -/4)(  creationTime>
valval creationTime: InstantInstant = InstantInstant.now(),
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
$1 )/#/ TransmogrifyingEntity #./# @TypeConverters ))*//$*)+*$)/$)"/*
TypeTransmogrifier<**(2$'' ' /*8)24/**)1 -//#/ Instant /*
.*( /#$)"/#/$/&)*2.#*2/*#)' > Long?.- .0'/<*0-/$( ./(+2$''
./*- $)$/ INTEGER *'0()?
Example: Locations
Location *% /*)/$).'/$/0 <'*)"$/0 <)+ -#+.*/# -1'0 .J ?"?<
'/$/0 K?!2 *)'4- *0//# '/$/0 )'*)"$/0 <2 *0'.1 /#*. $)/#
/. $).$)"' TEXT *'0()<.*'*)".2 ) / -($) "**!*-(//*0.
!*-/#/./-$)"?) +*..$$'$/4$./*#1 /# /2*1'0 .. +-/ 4. ($*'*)?
#/$.2#//# . /2*/4+ *)1 -/ -!0)/$*).*) TypeTransmogrifier *>
ROOM AND CUSTOM TYPES
48
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@TypeConverter
funfun locationToString(location: LocationLocation?) =
location?.let { "${it.latitude};${it.longitude}" }
@TypeConverter
funfun stringToLocation(location: StringString?) = location?.let {
valval pieces = location.split(';')
ifif (pieces.size == 2) {
trytry {
LocationLocation(nullnull asas StringString?).apply {
latitude = pieces[0].toDouble()
longitude = pieces[1].toDouble()
}
} catchcatch (e: ExceptionException) {
nullnull
}
} elseelse {
nullnull
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
0- )/$/4'..#. Location +-*+ -/4>
valval location: LocationLocation = LocationLocation(nullnull asas StringString?).apply {
latitude = 0.0
longitude = 0.0
},
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
**(2$''&)*2#*2/**)1 -/ Location /*)!-*( String<.**0-'*/$*)
2$'' ./*- $)$/ TEXT *'0()?
*2 1 -</# *2).$ *!0.$)"/#$.++-*#$./#/2 ))*/- $'4. -#. 
*)'*/$*)?!4*0-'*/$*)/$.)*/. -#' +-*+ -/4<)$/( - '4) ./*
 1$'' 2# )4*0'*4*0- )/$/$ .!-*(/# /. <0.$)"/4+ *)1 -/ -
'$& /#$.$.8) ? / -$)/#$.#+/ -<2 2$''. )*/# -++-*#J@EmbeddedK/#/
''*2.0./*./*- /# '/$/0 )'*)"$/0 .. +-/ *'0().2#$' ./$''(++$)"
/# (/*.$)"' data class $)*/'$)?
Example: Simple Collections
TEXT ) BLOB *'0().- 1 -4: 3$' ?*'*)".4*0)(-.#'4*0-/$)/*
ROOM AND CUSTOM TYPES
49
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
String *-4/ --4<4*0).1 /#//$) TEXT ) BLOB *'0().?.2$/#/#
*((A. +-/ 1'0 .++-*#$)/# +-  $)". /$*)</#*0"#<*'0().0. 
/#$.24- +**-!*-. -#$)"?
*<.0++*. /#/4*0#1  Set *! String 1'0 ./#/4*02)//*./*- <+ -#+.
- +- . )/$)"/"./*..*$/ 2$/#) )/$/4?) ++-*#$./*#1 . +-/ Tag
)/$/4) . /0+- '/$*)?#$.$./#  ./++-*#$)()4. .?0/<+ -#+.
4*0*)*/2)//**/#/!*-.*( - .*)?
*0)0. /4+ *)1 -/ -<0/4*0) /* $ #*2/*- +- . )/4*0-/$)
*'0()?!4*0-  -/$)/#//# /".2$'')*/*)/$).*( .+ $8#-/ -J ?"?<
*((K<4*0)0. /#  '$($/ A'$./++-*# (*)./-/ 2$/#'*/$*).$)
/# +-  $)". /$*)?!4*0) (*- : 3$$'$/4/#)/#/<4*0)'24.0.
 )*$)"<./# . /4+ *)1 -/ -.*>
@TypeConverter
funfun stringSetToString(list: SetSet<StringString>?) = list?.let {
valval sw = StringWriterStringWriter()
valval json = JsonWriterJsonWriter(sw)
json.beginArray()
list.forEach { json.value(it) }
json.endArray()
json.close()
sw.buffer.toString()
}
@TypeConverter
funfun stringToStringSet(stringified: StringString?) = stringified?.let {
valval json = JsonReaderJsonReader(StringReaderStringReader(it))
valval result = mutableSetOf<StringString>()
json.beginArray()
whilewhile (json.hasNext()) {
result += json.nextString()
}
json.endArray()
result.toSet()
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
ROOM AND CUSTOM TYPES
50
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
 - <2 0. /# JsonReader ) JsonWriter '.. ./#/#1  )+-/*!)-*$
.$)  1 'PP?'/ -)/$1 '4<4*0*0'0. /#$-A+-/4'$--4J ?"?<.*)<
*.#$K?
$1 )/# . /4+ *)1 -.$*)!0)/$*).<2 )0.  Set *! String 1'0 .$)
TransmogrifyingEntity>
valval tags: SetSet<StringString> = setOf()
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G-).(*"-$!4$)")/$/4?&/K
@2# - /# tags 2$'' ./*- $) TEXT *'0()?
Embedded Types
$/#/4+ *)1 -/ -.<2 - / #$)"**(#*2/* '2$/#0./*(/4+ .<0/2
- '$($/ /*(++$)"!-*(*) +-*+ -/4/**) *'0()?#/+-*+ -/4($"#/
*(+' 3<0/$/./$''"* .$)/**) *'0()$)/# /' ?
#/#++ ).</#*0"#<2# )2 #1 (0'/$+' *'0()./#/.#*0'*($) /*
- / .$)"' +-*+ -/4C
)/#/. <2 )0. /# @Embedded ))*//$*)*).*( data class *-*/# -
.$(+' */'$)'..</# )0. /#/'.../4+ $)) )/$/4?
Example: Locations
*- 3(+' <.2.)*/  -'$ -$)/#$.#+/ -<-(($)"'*/$*)$)/*.$)"'
TEXT *'0()2*-&.<0/2 ))*/- $'4,0 -4*)/# - .0'/$)"*'0()?!2 2)/
/*,0 -4!*-'*/$*).) -.*( '*/$*)$)/# /. <$/2*0' (0#(*-
*)1 )$ )//*#1 /# '/$/0 )'*)"$/0 ./*- .$)$1$0' REAL *'0().?
0/<0.$)"/4+ *)1 -/ -.<2 ))*/(+/2**'0()./**) +-*+ -/4?
$/# @Embedded<2 )<.2 ). $)/# EmbeddedLocationEntity '..$)/#
/# MiscSamples (*0' >
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport android.location.Locationandroid.location.Location
importimport android.util.JsonReaderandroid.util.JsonReader
importimport android.util.JsonWriterandroid.util.JsonWriter
importimport androidx.room.*androidx.room.*
ROOM AND CUSTOM TYPES
51
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
importimport org.threeten.bp.Instantorg.threeten.bp.Instant
importimport java.io.StringReaderjava.io.StringReader
importimport java.io.StringWriterjava.io.StringWriter
data classdata class LocationColumnsLocationColumns(
valval latitude: DoubleDouble,
valval longitude: DoubleDouble
) {
constructorconstructor(loc: LocationLocation) : thisthis(loc.latitude, loc.longitude)
}
@Entity(tableName = "embedded")
data classdata class EmbeddedLocationEntityEmbeddedLocationEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong = 0,
valval name: StringString,
@Embedded
valval location: LocationColumnsLocationColumns
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM embedded")
funfun loadAll(): ListList<EmbeddedLocationEntityEmbeddedLocationEntity>
@Insert
funfun insert(entity: EmbeddedLocationEntityEmbeddedLocationEntity)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G(  */$*))/$/4?&/K
 - <2 #1  LocationColumns '../#/2-+.*0-'/$/0 )'*)"$/0 ?#
)/$/4$/. '!#. LocationColumns +-*+ -/4<)(  location<(-& 2$/#/#
@Embedded ))*//$*)?*2<**(2$''0. $)$1$0' REAL *'0().!*-*0-'/$/0
)'*)"$/0 >
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS embedded (id INTEGER PRIMARYPRIMARY KEYKEY AUTOINCREMENT NOTNOT NULLNULL,
name TEXT NOTNOT NULLNULL, latitude REAL NOTNOT NULLNULL, longitude REAL NOTNOT NULLNULL)
.- .0'/<2 *0'*)./-0/,0 -$ .*)/#*. *'0().<$!2 2$.# ?
*/ /#/ 1 )/#*0"#'..0. $) @Embedded<'$& LocationColumns<$.)*/)
)/$/4<4*0)./$''0. @ColumnInfo ))*//$*).*)$//*- )( *'0().<$!
 .$- ?'.*<4*0- .0% //*/# .( -0' .!*-//4+ .>/# +-*+ -/$ .) /*
 /4+ ./#/**(0) -./).<)/$1 '4*-1$/4+ *)1 -/ -./#/4*0- / ?
ROOM AND CUSTOM TYPES
52
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Simple vs. Prefixed
#/#++ ).$!2 )  two '*/$*).</#*0"#C -#+.2 )  officeLocation
) affiliateLocation<*-.*( /#$)"'$& /#/?
4 !0'/<**(" ) -/ .*'0())( .. *)/# @Embedded '..D+-*+ -/4
)( .<+ -#+.(*$8 4 @ColumnInfo ))*//$*).*)/#*. +-*+ -/$ .?)/#$.
. </#*0"#<$!2 #1 /2* LocationColumns +-*+ -/$ .$)/# .(  )/$/4'..<2
2*0'2$)0+2$/#/2* latitude )/2* longitude *'0().<2#$#) $/# -**(
)*-$/ 2$''.0++*-/?
*- ../#$.</# @Embedded ))*//$*) +/.)*+/$*)' prefix +-*+ -/4>
@Embedded(prefix = "office_")
valval officeLocation: LocationColumnsLocationColumns
# *'0().!*-/#/ )/$/42$''#1 /# prefix  >
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS embedded (id INTEGER PRIMARYPRIMARY KEYKEY AUTOINCREMENT NOTNOT NULLNULL,
name TEXT NOTNOT NULLNULL, office_latitude REAL NOTNOT NULLNULL, office_longitude REAL NOTNOT NULLNULL)
 ) <#1$)"/2* LocationColumns .$(+'4( )./#/*) *-*/#) /*0.
$./$)/ prefix 1'0 .?
*2 1 -< -$)($)/#//#$.#)" ./# *'0())( .<.*4*02$'''.*) /*
%0./)4 @Query !0)/$*)/#/- ! - ) ./#*. )( .<.*/#/4*00. /#
++-*+-$/ +- 83?
ROOM AND CUSTOM TYPES
53
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room and Reactive Frameworks
/. G) .'*2<+-/$0'-'4!*-'-" -/. .)0)*+/$($5 
*+ -/$*).?.- .0'/<2 $)1-$'42)//**/#//. G*)&"-*0)
/#- .?
# - - ()4*+/$*).!*-*$)"/#/<$)'0$)".$(+'4.+$))$)"0+4*0-*2)
Thread *- Executor /#/4*00. /*(& 4*0-''.?*2 1 -</# (*-
+*+0'-24*!- ..$)"/#$.)*24.$./*0. E- /$1 !-( 2*-&.F<2#$#2-+
0+/#- $)")- .0'/. '$1 -4!*-4*0?)/#$.#+/ -<2 2$'' 3($) **(D.
.0++*-/!*-! 2- /$1 *+/$*).> LiveData<*/'$)*-*0/$) .<)31?
0/<8-./<2*-!-*(*0-?
Room and the Main Application Thread
*0($"#/)*/ 2*--$ .*(0#*0//# .+ *!4*0-/. G?4 4*0
/#$)&/#/4*0-/. 2$'') 1 -" /'-" ?4 4*0/#$)&/#/4*0-0. -.2$''''
 0.$)" 3+ ).$1  1$ .J)/#$)&/#/ 3+ ).$1  1$ .( )./#//# 42$''
#1 !.//. GK?4 /#- .(& 4*0-# #0-/?
 ) <4*0($"#//#$)&/#/4*0)%0./"*# )0. **(*)/# ($)
++'$/$*)/#- < .+$/ /# !//#/$/2$''!- 5 4*0-2#$' /#//. G
$."*$)"*)?!/ -''<$)/# * /#/2 #1 . ).*!-$)/# **&<2 #1 )*/
0.  Thread< Executor<*-)4!)4- /$1 !-( 2*-&H2 #1 %0./( /#
/. ''.?
0//#/$. 0. / ./!0)/$*).- '' *)&"-*0)/#- 0/*(/$''4?
*)*/#1 /*!*-&&"-*0)/#- *!*0-*2)?
55
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*. #*2**( #1 .*)/# ($)++'$/$*)/#- <2 #1 /*2-$/ *0-/ ./
/*0. /# ($)++'$/$*)/#- <.0#.1$ runOnMainSync()>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
importimport org.junit.Assert.*org.junit.Assert.*
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass MainAppThreadGoBoomTestMainAppThreadGoBoomTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.autoGenerate()
@Test
funfun goBoom() {
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().runOnMainSync {
assertThat(underTest.loadAll(), isEmpty)
valval original = AutoGenerateEntityAutoGenerateEntity(id = 0, text = "This will get its own ID")
valval inserted = underTest.insert(original)
assertTrue(original === inserted)
assertThat(inserted.id, !equalTo(0L))
}
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G$)++#- ***( ./?&/K
#$.$.'*) *!/# / ./2 #!*-0.$)" @PrimaryKey(autoGenerate = true) *)
) )/$/4< 3 +//#//# / ./* $.2-++ $)''/* runOnMainSync()</*!*-
$//*-0)*)/# ($)++'$/$*)/#- ?#$' /# *-$"$)'/ ./.0 .</#$.*)
-.# .2$/#>
java.lang.IllegalStateException: Cannot access database on the main thread since it
may potentially lock the UI for a long period of time.
#  1 '*+ -.2#*- / **('*&**(0." *)/# ($)++'$/$*)
/#- <./#/$.))/$A+// -)?
ROOM AND REACTIVE FRAMEWORKS
56
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*<2 ) /**.*( /#$)"/*1*$/#$..*-/*!-.#?
Room and LiveDataLiveData
) E*0/*!/# *3F*+/$*)$./*0. LiveData?
*0)' -)(*- *0/ LiveData $)/# N#$)&$)"*0/
#- .)$1 /N#+/ -*! Elements of Android JetpackB
*.+ $' + ) )$ .- - ,0$- <*/# -/#)**($/. '!?*0).$(+'42-+
4*0- @Query - /0-)1'0 .$) LiveData>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport androidx.lifecycle.LiveDataandroidx.lifecycle.LiveData
importimport androidx.room.*androidx.room.*
importimport kotlinx.coroutines.flow.Flowkotlinx.coroutines.flow.Flow
importimport org.threeten.bp.Instantorg.threeten.bp.Instant
importimport java.util.*java.util.*
@Entity(tableName = "todos", indices = [IndexIndex(value = ["id"])])
data classdata class ToDoEntityToDoEntity(
valval description: StringString,
@PrimaryKey
valval id: StringString = UUIDUUID.randomUUID().toString(),
valval notes: StringString = "",
valval createdOn: InstantInstant = InstantInstant.now(),
valval isCompleted: BooleanBoolean = falsefalse
) {
constructorconstructor(model: ToDoModelToDoModel) : thisthis(
id = model.id,
description = model.description,
isCompleted = model.isCompleted,
notes = model.notes,
createdOn = model.createdOn
)
funfun toModel(): ToDoModelToDoModel {
returnreturn ToDoModelToDoModel(
id = id,
description = description,
isCompleted = isCompleted,
ROOM AND REACTIVE FRAMEWORKS
57
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
notes = notes,
createdOn = createdOn
)
}
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM todos")
funfun all(): LiveDataLiveData<ListList<ToDoEntityToDoEntity>>
@Query("SELECT * FROM todos WHERE id = :modelId")
funfun find(modelId: StringString): LiveDataLiveData<ToDoEntityToDoEntity?>
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
funfun save(varargvararg entities: ToDoEntityToDoEntity)
@Delete
funfun delete(varargvararg entities: ToDoEntityToDoEntity)
}
}
J!-*( $1 /G.-G($)G%1G*(G*((*).2- G/**G- +*G**)/$/4?&/K
#$. ToDoEntity '..$.!-*( /# LiveData (*0' *! /# **&D.+-$(-4.(+'
+-*% /?
*0)' -)(*- *0//# /*A*++'$/$*)/#$.(*0' $.
. *)$)/# N#/ - 0$'$)"N#+/ -*! Exploring
AndroidB
#1  @DaoA))*// $)/ -! )(  ToDoEntity.Store?/#.!*0-!0)/$*).<
/2**!2#$##1 @Query ))*//$*).Jall() ) find()K?)./ *!- /0-)$)"
)/$/$ .$- /'4</#*0"#</# 4- /0-) LiveData 2-++ -.-*0)/#*.  )/$/$ .?
# )2 '' all() *- find()<2 " / LiveData &$(( $/ '4?# G2$''
)*/ + -!*-( 0)/$'2 observe() /#/ LiveData?//#/+*$)/</# /. G
2$'' *)0/ *)**(A.0++'$ &"-*0)/#- <0//# - .0'/.2$''
 '$1 - /**0- Observer *)/# ($)++'$/$*)/#- J.2$/#''0. .*!
LiveDataK?
Benefits of LiveDataLiveData
.2$/#''*!*0-- /$1 *+/$*).</# /. G" /.*;* /*&"-*0)
ROOM AND REACTIVE FRAMEWORKS
58
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/#- <.*2 *)*/) /*!*-&.0#/#- *0-. '1 .? /<2 ./$''" //# - .0'/.
 '$1 - /*0.*)/# ($)++'$/$*)/#- <.*2 ) .$'4++'4/#*. - .0'/.
/**0-?
'.*< LiveData * .)*/- ,0$- )4$/$*)' + ) )$ .<2#$#)# '+/*
& +/# ++$/.('' -?
Issues with LiveDataLiveData
LiveData $.)*/)*+/$*)!*- @Insert< @Update<*- @Delete !0)/$*).H4*02*0'
./$'') /*()" 4*0-*2)&"-*0)/#- .!*-/#*. ?
LiveData '24. '$1 -.$/.- .0'/.*)/# ($)++'$/$*)/#- ?#$.$."- /
2# )2 2)//#*. - .0'/.*)/# ($)++'$/$*)/#- ?#$.$.+-*' (2# )
2 * not 2)//#*. - .0'/.*)/# ($)++'$/$*)/#- <.0#.+ -!*-($)"
.*( /. G$)+- +-/$*)!*-(&$)" . -1$ - ,0 ./?
LiveData ) ./* *. -1 <)/# /4+$'24*!*. -1$)" LiveData - ,0$- .
/#/4*0+.. LifecycleOwner /* observe()?#$.$.))*4$)"2# )4*0*)*/
#1  LifecycleOwner /*0. <.0#.2# )0.$)" LiveData $).*( /4+ .*!
. -1$ .?*0)0. observeForever() /*1*$/# ) !*-/# LifecycleOwner<
0//# )4*0) /*- ( ( -/*- (*1 4*0- Observer<' ./4*0$ )/''42$)
0+2$/#( (*-4' &?
) LiveData $.'$"#/2 $"#/4 .$")?!4*0#1 *(+' 3&"-*0)*+ -/$*).
/*+ -!*-(<.0#.*($)$)"- .0'/.!-*((0'/$+' .*0- .)*)1 -/$)"/#*.
- .0'/.$)/*.+ $8*% //4+ .< LiveData #.'$($/ !$'$/$ ./*# '+2$/#/#/?
)<2#/$/* .#1 ) .*( 2#/-) /*0. J ?"?< MediatorLiveDataK?
Room and Coroutines
*-*/'$) 1 '*+ -.</# ' $)"- /$1 .*'0/$*)$.*/'$)D.*2)*-*0/$) .
.4./ (?#$.$.$- / 3/ ).$*)*!/# +-*"-(($)"')"0" )*7 -./# (*./
+*2 -2$/#/# ' ./.4)//$*(+' 3$/4?
*0)' -)(*- *0/*-*0/$) .$)/# N)/-*0$)"
*-*0/$) .N#+/ -*! Elements of Kotlin CoroutinesB
ROOM AND REACTIVE FRAMEWORKS
59
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
**(.0++*-/.*-*0/$) .1$/# androidx.room:room-ktx  + ) )4?$)"
/#/2$''+0''$)*(+/$' 1 -.$*)*!*-*0/$) .$)$/$*)/***(D.*2)*
!*-.0++*-/$)"*-*0/$) .$) @DaoA))*// $)/ -! .?
suspendsuspend
) 1)/" /#/**(D.*-*0/$) ..0++*-/#.*1 -$/. LiveData .0++*-/$./#/
4*0)0. *-*0/$) .2$/# @Insert< @Update<) @Delete?$(+'4/# suspend
& 42*-/*/# @DaoA))*// !0)/$*).>
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
suspendsuspend funfun save(varargvararg entities: ToDoEntityToDoEntity)
@Delete
suspendsuspend funfun delete(varargvararg entities: ToDoEntityToDoEntity)
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G**)/$/4?&/K
# . !0)/$*).- !-*( ToDoEntity.Store $(+' ( )//$*)$) /# Coroutines
(*0' *! /# **&D.+-$(-4.(+' +-*% /?#$.(*0' $.!0)/$*)''4/# .(
./# LiveData (*0' < 3 +//#/$/0. .*-*0/$) .2$/#**(?
# . suspend !0)/$*).)/# ) '' !-*()4.0$/' CoroutineScope<.0#
./# viewModelScope *7 - 4 /+&D. ViewModel .4./ (?
0././# LiveData @Dao !0)/$*).0. **(A.0++'$ &"-*0)/#- <.*/**
*/# suspend @Dao !0)/$*).?
FlowFlow
*- @QueryA))*// !0)/$*).<4*0#1 /2*#*$ .?*0*0'0.  suspend
!0)/$*)<%0./.4*0)2$/# @Insert< @Update<) @Delete?# '*. - ,0$1' )/
*!0.$)" LiveData</#*0"#<$./*0. Flow>
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM todos")
funfun all(): FlowFlow<ListList<ToDoEntityToDoEntity>>
@Query("SELECT * FROM todos WHERE id = :modelId")
funfun find(modelId: StringString): FlowFlow<ToDoEntityToDoEntity?>
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
ROOM AND REACTIVE FRAMEWORKS
60
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
suspendsuspend funfun save(varargvararg entities: ToDoEntityToDoEntity)
@Delete
suspendsuspend funfun delete(varargvararg entities: ToDoEntityToDoEntity)
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G**)/$/4?&/K
*0)' -)(*- *0/ Flow $)/# N)/-*0$)"'*2.)
#)) '.N#+/ -*! Elements of Kotlin CoroutinesB
Flow $.$/'$& LiveData<$)/#/4*0)*. -1 $/J1$!0)/$*).'$& collect()K
/*-  $1 4*0-- .0'/.?$& /# - ./*!*-*0/$) .<4*0)*)/-*'/# $.+/# -
/#/$// .2#//#- $.0. !*--  $1$)"/#*. - .0'/.?-<4*0)0. /#
asLiveData() 3/ ).$*)!0)/$*).0++'$ 4/# androidx.lifecycle:lifecycle-
livedata-ktx -/$!//**)1 -/ Flow $)/* LiveData !*-*).0(+/$*)4)
/$1$/4*-!-"( )/>
packagepackage com.commonsware.todo.ui.rostercom.commonsware.todo.ui.roster
importimport androidx.lifecycle.LiveDataandroidx.lifecycle.LiveData
importimport androidx.lifecycle.ViewModelandroidx.lifecycle.ViewModel
importimport androidx.lifecycle.asLiveDataandroidx.lifecycle.asLiveData
importimport androidx.lifecycle.viewModelScopeandroidx.lifecycle.viewModelScope
importimport com.commonsware.todo.repo.ToDoModelcom.commonsware.todo.repo.ToDoModel
importimport com.commonsware.todo.repo.ToDoRepositorycom.commonsware.todo.repo.ToDoRepository
importimport kotlinx.coroutines.flow.mapkotlinx.coroutines.flow.map
importimport kotlinx.coroutines.launchkotlinx.coroutines.launch
classclass RosterViewStateRosterViewState(
valval items: ListList<ToDoModelToDoModel> = listOf()
)
classclass RosterMotorRosterMotor(privateprivate valval repo: ToDoRepositoryToDoRepository) : ViewModelViewModel() {
valval states: LiveDataLiveData<RosterViewStateRosterViewState> =
repo.items().map { RosterViewStateRosterViewState(it) }.asLiveData()
funfun save(model: ToDoModelToDoModel) {
viewModelScope.launch {
repo.save(model)
}
}
}
ROOM AND REACTIVE FRAMEWORKS
61
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G0$G-*./ -G*./ -*/*-?&/K
#$. ViewModel JRosterMotorK$.)*/0.$)"/# Flow !-*(*0- ToDoEntity.Store
$- /'4?/# -<$/$.''$)" all() *) ToDoRepository<2#$#$)/0-)$.0.$)"
ToDoEntity.Store?# all() *) ToDoRepository /& .1)/" *!/# -$#. /*!
*+ -/*-.*) Flow /**)1 -/ )/$/$ ./*(* '*% /.>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport kotlinx.coroutines.CoroutineScopekotlinx.coroutines.CoroutineScope
importimport kotlinx.coroutines.flow.Flowkotlinx.coroutines.flow.Flow
importimport kotlinx.coroutines.flow.mapkotlinx.coroutines.flow.map
importimport kotlinx.coroutines.withContextkotlinx.coroutines.withContext
classclass ToDoRepositoryToDoRepository(
privateprivate valval store: ToDoEntityToDoEntity.StoreStore,
privateprivate valval appScope: CoroutineScopeCoroutineScope
) {
funfun items(): FlowFlow<ListList<ToDoModelToDoModel>> =
store.all().map { all -> all.map { it.toModel() } }
funfun find(id: StringString): FlowFlow<ToDoModelToDoModel?> = store.find(id).map { it?.toModel() }
suspendsuspend funfun save(model: ToDoModelToDoModel) {
withContext(appScope.coroutineContext) {
store.save(ToDoEntityToDoEntity(model))
}
}
suspendsuspend funfun delete(model: ToDoModelToDoModel) {
withContext(appScope.coroutineContext) {
store.delete(ToDoEntityToDoEntity(model))
}
}
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
Benefits of Coroutines
**(D.*-*0/$) ..0++*-/*1 -.'' @Dao !0)/$*).<2# - . LiveData *)'42*-&.
!*- @Query?
*-*0/$) .+-*1$ .: 3$$'$/4!*-/# /#- /#/4*00. /*-  $1 /# - .0'/.?
#$' *!/ )/$( .4*02$''0. Dispatchers.Main /*" //# - .0'/.*)/# ($)
++'$/$*)/#- J!*-0. K<4*0#1 /# *+/$*)*!0.$)"*/# -$.+/# -.!*-
ROOM AND REACTIVE FRAMEWORKS
62
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*/# -. )-$*.?
*-*0/$) .*1 -''- )*//$ /*)-*$</# 24/#/ LiveData $.?/2$'' (*-
*((*)/*. '$--$ . 3+*. *-*0/$) .A. /#)*) . *) LiveData?
.- .0'/<!*-/# *1 -''*/'$) *.4./ (<*-*0/$) .$.'$& '4/* '$+. LiveData
$)+*+0'-$/4<$!$/#.)*/*) .*'- 4?
Issues with Coroutines
*/'$)D.*-*0/$) ..4./ ($./# 4*0)" ./*!/# /#- ($)**(- /$1
!-( 2*-&.?..0#<$/#.)*/ )E / )0+F.(0#.<.4<31#.?
*-*0/$) .- /$ /**/'$)?#$' /# - - 24./*" /1* /*$)/ -*+ -/
2$/#*/'$)* /#/0. .*-*0/$) .<$/$.-/# -'0)&4?!4*0 3+ //*#1 '*/
*!1* ) $)"/*2*-&2$/#4*0- @Dao<4*0(4  // -*72$/# LiveData *-
31<0)/$'.0#/$( .4*0)(*1 /**-*0/$) .?
Room and RxJava
# '..$- /$1 .*'0/$*)!*-1$.31?31Q$./# (*./+*+0'-1 -.$*)<
)$/*7 -.-$#A0/A*(+' 3. /*!/4+ .!*-- /$1 - .0'/. '$1 -4?**(<1$
/# androidx.room:room-rxjava2 -/$!/<.0++*-/.()4*!/# . <$)'0$)"
Flowable< Observable< Single< Maybe<) Completable?-<2 )0. )'+#
$/$*)*! androidx.room:room-rxjava3<!*-31R<2#$# 0/ $)QOQO?
*0-- .0'/.A- /0-)$)" @Dao !0)/$*).($"#/0. .*( /#$)"'$& Observable *-
Maybe<2#$' 4*0-*/# - @Dao !0)/$*).)0. Completable>
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM todos")
funfun all(): ObservableObservable<ListList<ToDoEntityToDoEntity>>
@Query("SELECT * FROM todos WHERE id = :modelId")
funfun find(modelId: StringString): MaybeMaybe<ToDoEntityToDoEntity>
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
funfun save(varargvararg entities: ToDoEntityToDoEntity): CompletableCompletable
@Delete
funfun delete(varargvararg entities: ToDoEntityToDoEntity): CompletableCompletable
}
ROOM AND REACTIVE FRAMEWORKS
63
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( 3G.-G($)G%1G*(G*((*).2- G/**G- +*G**)/$/4?&/K
)'$& 2$/#*-*0/$) .) LiveData<**(#.$)/ -($// )/.0++*-/!*-
0/*(/$''4+0//$)"4*0-312*-&*).# 0' -2$/#&"-*0)/#- .?
**(2$''+0/4*0- Observable 2*-&*)$/.*2).# 0' -<0/)*/ Maybe *-
Completable<!*- 3(+' ?*-/#*. <4*02$'') /*.0++'44*0-*2).# 0' -<
.0#. Schedulers.io()>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport io.reactivex.Maybeio.reactivex.Maybe
importimport io.reactivex.Observableio.reactivex.Observable
importimport io.reactivex.schedulers.Schedulersio.reactivex.schedulers.Schedulers
classclass ToDoRepositoryToDoRepository(privateprivate valval store: ToDoEntityToDoEntity.StoreStore) {
funfun items(): ObservableObservable<ListList<ToDoModelToDoModel>> = store.all()
.map { all -> all.map { it.toModel() } }
funfun find(id: StringString): MaybeMaybe<ToDoModelToDoModel> = store.find(id)
.map { it.toModel() }
.subscribeOn(SchedulersSchedulers.io())
funfun save(model: ToDoModelToDoModel) = store.save(ToDoEntityToDoEntity(model))
.subscribeOn(SchedulersSchedulers.io())
funfun delete(model: ToDoModelToDoModel) = store.delete(ToDoEntityToDoEntity(model))
.subscribeOn(SchedulersSchedulers.io())
}
J!-*( 3G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
/# -2$. <4*02$''" //# E))*/ ../. *)/# ($)/#- F --*-?
 4*)/#/</#*0"#<4*0)/# ) subscribe() /*/# . 31/4+ .)*).0(
/# $-- .0'/..4*0. 8/?
Benefits of RxJava
31$./# (*./. .*) *!/# . - /$1 .*'0/$*).<.*$/$.(*- '$& '4/#/4*0
2$''8)$)!*-(/$*)*0/)4+-*' (./#/4*0 )*0)/ -?
$& *-*0/$) .<31"$1 .4*0: 3$$'$/4!*-.0++'4$)".# 0' -*)2#$#/*
*. -1 /# - .0'/.?#$.$.$)*)/-.//* LiveData<2#$#'24. '$1 -.$/.- .0'/.
*)/# ($)++'$/$*)/#- ?
ROOM AND REACTIVE FRAMEWORKS
64
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Issues with RxJava
31#.1 -4./ +' -)$)"0-1 ?$/# - LiveData *-*-*0/$) .2$'' .$(+' -
!*-) 2*( -./*+$&0+)0. ?
Observable Queries
*- @QueryA))*// !0)/$*).- /0-)$)" LiveData< Flow< Observable<*- Flowable<
**(2$'' '$1 -#)" .*1 -/$( ?*02$''" /*) - .0'/!-*(/# ,0 -4$)$/$''4?
!4*0*)/$)0 /**. -1 /# - /$1 /.*0- </#*0"#<)4*0(*$!4/#
/. 1$**(<4*02$''" / fresh results  '$1 - /*4*00/*(/$''41$/#
- /$1 /.*0- ?
*<!*- 3(+' <$!4*0#1 !-"( )/*. -1$)",0 -4)0.$)"/#///*
+*+0'/ '$./<)*/# -* $)/#/!-"( )/$). -/.) 2-*2$)/*/# /. <
4*0-!-"( )/2$''" /!- .#,0 -4- .0'/2$/#*0/#1$)"/*()0''4- A- ,0 ./$/?
#$.$.1 -4*)1 )$ )/$)()4. .? -$)($)</#*0"#</#/4*0%0./" /!- .#
- .0'/2$/#*0/)4*)/ 3/?*<$)/#  3(+' !-*(/# +- 1$*0.+-"-+#<2#$'
4*0" /!- .#,0 -4- .0'/<4*0- )*//*' 3/'42#/#)" $)/#/- .0'/@$!
)4/#$)"?*- 3(+' <4*0($"#/ $). -/$)"-*2/#/$.)*/$)'0 $)/#
,0 -4- .0'/ 0. $/!$' /*(/# WHERE '0. ?
*)1 -. '4<4*0($"#/" /) 2- .0'/ '$1 - /*4*02# - )*/#$)"/0''4
#)" ?0++*. 4*0#1 )*0/./)$)",0 -42$/# WHERE price > 10 $)/#
<)4*0$). -/) 2-*2/*/#//' 2# - price $. 5?**(2$'' '$1 -4*0
!- .#- .0'/- +- . )/$)"/# /#)" $)/# /' @0/.$) /#$.) 2-*2* .
)*/(/#4*0- WHERE '0. </# E!- .#F- .0'/2$'' /# .( ./# './- .0'/?
*0*0'0. distinctUntilChanged() *) Flow *- Observable /*8'/ -*0//#*.
0+'$/ - .0'/.<$! .$- ?
Room and ListenableFutureListenableFuture
**('.*.0++*-/. ))-*$ $/$*)*!01D. ListenableFuture $)/ -! ?
*0)#1 !0)/$*).- /0-) ListenableFuture $)./ *!)31
Single<!*- 3(+' ? ListenableFuture ''*2.4*0/*- "$./ -''&/*8)*0/
2# )/# 2*-&$.*(+' / ?
#$.*+/$*)$.- '/$1 '4*.0- )#.1 -4'$//' *0( )//$*)?)' ..4*0-
ROOM AND REACTIVE FRAMEWORKS
65
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
$)/ "-/$)"2$/#.*( !-( 2*-&/#/$/. '!$.. *) Future )
ListenableFuture</# */# -- /$1 .*'0/$*).+- . )/ $)/#$.#+/ --
.0./)/$''4(*- +*+0'-?
Where Synchronous Room is Safe
*'*)".4*0*4*0-**(/. G*)&"-*0)/#- <**(* .)*/
- 2# - /#/&"-*0)/#- ( !-*(?**($.)*/!*-$)"4*0/*0. .*(
**(A.0++'$ /#- H$/$.%0./!*-$)"4*0/*0. .*( &"-*0)/#- ?
 ) <4*0- 2 '*( /*0. )*)A- /$1 **( @Dao *+/$*).!-*(+' .2# -
4*0'- 4#1 &"-*0)/#- <.0#. Worker !*-0. 2$/# WorkManager *-
JobIntentService?
Being Evil
*(  1 '*+ -.*)*/'$& /*/& E)*F!*-)).2 -?# )/# 4- /*'/#//# 4
.#*0')*/*/. G*)/# ($)++'$/$*)/#- H)/#/**(
/$1 '4'*&./#/ #1$*-H/# 4" /)"-4?
*-/#*.  1 '*+ -.</# - $. allowMainThreadQueries()?
#$.$.( /#**) RoomDatabase.Builder /#/4*0)''/*/0-)*7/# )*)
**(''.*)/# ($)++'$/$*)/#- >
privateprivate valval db = RoomRoom.databaseBuilder(
myContext,
MiscDatabaseMiscDatabase::classclass.java,
DB_NAMEDB_NAME
)
.allowMainThreadQueries()
.build()
*2< db ) 0. *)/# ($)++'$/$*)/#- <2$/#*0/*(+'$)/.!-*(
**(?*2 1 -</# - might  *(+'$)/.!-*(0. -.<*-()" ( )/<*-/# 
/ (<0 /*/# ++++ -$)"/*+ -!*-(+**-'4?
ROOM AND REACTIVE FRAMEWORKS
66
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Relations in Room
$/ $.- '/$*)'/. ?*!-<2 #1 )*//'& *0//#/<!*0.$)"
$)./ *)./)'*)  )/$/$ .?
**(.0++*-/. )/$/$ . $)"- '/ /**/# -*)/ )/$)*/# -/' .?**(* .
not .0++*-/ )/$/$ . $)"$- /'4- '/ /**/# - )/$/$ .</#*0"#?
)$!/#/.*0)../-)" </# - $.E( /#*/*/# () ..F?
J*-<$)*/'$)<E!0)/$*)/*/# !**'$.#) ..FK
)/#$.#+/ -<2 2$'' 3+'*- #*24*0$(+' ( )/- '/$*)'./-0/0- .2$/#**(
)2#4**(#./# - ./-$/$*)./#/$/* .?
The Classic ORM Approach
1.#1 '*)".0++*-/  )/$/$ .#1$)"- '/$*)./**/# - )/$/$ .</#*0"#
)*/ 1 -40. ./# E )/$/4F/ -(?
) )-*$/#/* .$. "- )?/''*2.4*0/*0. ))*//$*)./*
$)$/ - '/$*).<.0#.$)/#$.1.)$++ />
@Entity
publicpublic classclass ThingyThingy {
@Id privateprivate LongLong id;
privateprivate long otherThingyId;
@ToOne(joinProperty="otherThingyId")
privateprivate OtherThingyOtherThingy otherThingy;
67
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
// other good stuff here
}
@Entity
publicpublic classclass OtherThingyOtherThingy {
@ID privateprivate LongLong id;
}
# . ))*//$*).- .0'/$) getOtherThingy() ) setOtherThingy() ( /#*./*
 .4)/# /$''4 /* Thingy J*-<(*- 0-/ '4</*#$ ).0'..*!
Thingy<0/!*-/# +0-+*. .*!/#$.. /$*)<2 2$''$")*- /#/K?#$#
OtherThingy *0- Thingy - '/ ./*$./$ /*/#/ otherThingyId 8 '<2#$#$.
./*- .*'0()$)/# /' ?# )4*0'' getOtherThingy()<"- )2$''
,0 -4/# /. /*'*$)/# OtherThingy $)./) <..0($)"/#/$/#.)*/
 )# '- 4?
#/$.2# - /# /#- $)"+-*' (- +.$)?
A History of Threading Mistakes
))-*$++ 1 '*+( )/<2 - *)./)/'4#1$)"/*8"#//*& +$.&G*7
*!/# ($)++'$/$*)/#- ?1 -4($''$. *)/#/*0-*  3 0/ .*)/#
($)++'$/$*)/#- $.($''$. *)/#//# ($)++'$/$*)/#- $.)*/
0+/$)"*0-?0-*% /$1 $./*(*1 .(0#$.&G.+*..$' *7/# ($)
++'$/$*)/#- ?#/$.2#42 0. ''/#*. )$ - /$1 .*'0/$*).!-*( /#
+-  $)"#+/ -?
# +-*' ($./#//# )$  )+.0'/$*)/#/2 " /!-*(*% /A*-$ )/ 
+-*"-(($)"'.* )+.0'/ .&)*2' " *!2# /# -$.&G2$'' *) 2# )
2 ''+-/$0'-( /#*?
'..$0. *! SQLiteDatabase )*0)/ -./#$.2$/#/# rawQuery()Gquery() !($'4
*!( /#*.?# 4- /0-) Cursor?*0($"#//#$)&H- .*)'4H/#//#*.
( /#*. 3 0/ /# ,0 -4/#/4*0- ,0 ./?)/-0/#</# 4*)*/?''/# 4*
$.- /  SQLiteCursor $)./) /#/#*'.*)/*/# ,0 -4)/#
SQLiteDatabase?/ -<2# )4*0''( /#*/#/- ,0$- ./# /0',0 -4- .0'/
J ?"?< getCount()</*" //# )0( -*!- /0-) -*2.K< then /# ,0 -4$. 3 0/ 
"$).//# /. ?.- .0'/<''/# 2*-&/#/4*0*/*'' rawQuery() *-
query() *)&"-*0)/#- " /.2./ $!4*0*)*/ also *.*( /#$)"/*
!*- /# ,0 -4/*  3 0/ *)/#/.( &"-*0)/#- ?/# -2$. <4*0(4
RELATIONS IN ROOM
68
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
2$)0+2$/#/# ,0 -4 $)" 3 0/ *)/# ($)++'$/$*)/#- <2$/#
$(+/.*)/# ?
"- )- '/$*).)2*-&/# .( 24?!4*0- /-$ 1 4*0- Thingy *)
&"-*0)/#- </# )'' getOtherThingy() *)/# ($)++'$/$*)/#- <
 + )$)"*)2#/ '. #.''*0-- < getOtherThingy() ($"#/) /*+ -!*-(
/. ,0 -4@2#$#4*0*)*/2)/*)/# ($)++'$/$*)/#- ?
The Room Approach
**( #1 .$/'$& */# -))*//$*)A. )-*$.<0/2# )$/*( .
/*- '/$*).<**( +-/.!-*()*-(.<$)) 7*-//*- 0 /# '$& '$#***!
/#- $)"+-*' (.?
)'$& /# "- ) 3(+' *1 <2$/#**(< Thingy ))*/#1 +-*+ -/4
!*-) OtherThingy /#/**($. 3+ / /*()" ?*0*0'#1 +-*+ -/4!*-
) OtherThingy (-& . @Ignore<0//# )4*0- *)4*0-*2)!*- '$)"2$/#
/#/+-*+ -/4?
# $(+'$/$*)*!) )/$/4- ! - )$)")*/# - )/$/4$- /'4$./#/ 1 '*+ -.
2*0' 3+ //#/2# )**(- /-$ 1 ./# *0/ - )/$/4</#/**( $/# -2$''
0/*(/$''4- /-$ 1 /# $)) - )/$/4*-2$''- /-$ 1 $/'5$'4'/ -*)?# !*-( -
++-*#1*$./#- $)"$..0 .0/-0)./# -$.&*!'*$)"(*- //#)$.
)  ..-4?# '// -++-*#-0)./# -$.&*!/-4$)"/**$.&G*)/# ($)
++'$/$*)/#- ?
#$.* .)*/( )/#/4*0))*/#1 !*- $")& 4.?**(!0''4.0++*-/.!*- $")
& 4- '/$*).#$+.<424*! @ForeignKey ))*//$*)?#$.. /.0+/# !*- $")& 4.
$)/# ++-*+-$/ /' .@0//#/D.*0/$/?**('.*#. @Relation
))*//$*)</*''*24*0/*- /-$ 1 - '/ /@0/$/* .)*/$)1*'1  )/$/$ ..
(0#.4*0($"#//#$)&?
)''*!/#/2$''J#*+ !0''4K(& (*- . ). 2$/#.*(  3(+' .<!-*(/# /#
MiscSamples (*0' *! /# **&D.+-$(-4.(+' +-*% /?
One-to-Many Relations
 /D.$("$) /#/2 - . //$)"0+)++!*-**&/'*"?# /'*"$.$1$ 
$)/*/ "*-$ .<)/ "*-$ .)#1 **&.?*<2 )  Category )/$/4/*
(* '/# / "*-$ .<)2 )  Book )/$/4/*(* '/# **&.?.+-/*!/#$.<
RELATIONS IN ROOM
69
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
2 '.*) /*(* '/# *) A/*A()4- '/$*).#$+ /2 ) Category ) Book?
)/#$.. < Category $/. '!* .)*/) )4/#$)".+ $'?/$.%0./)*-$)-4
**( )/$/4>
packagepackage com.commonsware.room.misc.onetomanycom.commonsware.room.misc.onetomany
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "categories")
data classdata class CategoryCategory(
@PrimaryKey
valval shortCode: StringString,
valval displayName: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*) /*()4G/ "*-4?&/K
*/ </#*0"#</#/$/* .)*/#1  List *- Array *-*/# -*'' /$*)*! Book
*% /.?*0))*/.&/ "*-4!*-$/.**&.</' ./)*/4.&$)"/#  )/$/4?
Configuring the Foreign Key
Book<)*0-<- 2# - /#$)"../-//*" /$)/ - ./$)"?
# Book '..<$)$.*'/$*)<$.*0/.+'$).$. Category>
data classdata class BookBook(
@PrimaryKey
valval isbn: StringString,
valval title: StringString,
@ColumnInfo(index = truetrue) varvar categoryShortCode: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*) /*()4G**&?&/K
# *)'4/#$)"$)/ - ./$)"# - $./#/2  '- )$) 3*) categoryShortCode?
./# )( .0"" ./.</#$.#*'./# shortCode +-$(-4& 4*!/# Category /#/$.
..*$/ 2$/#/#$. Book?*/ /#/ Book * .)*/#1 )/0'+-*+ -/4!*-/#
Category<%0./$/.& 4?
# )2 .-*''0+/# .*0- * $/)'**&//# @Entity ))*//$*)<2
)*0)/ - @ForeignKey>
RELATIONS IN ROOM
70
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misc.onetomanycom.commonsware.room.misc.onetomany
importimport androidx.room.ColumnInfoandroidx.room.ColumnInfo
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.ForeignKeyandroidx.room.ForeignKey
importimport androidx.room.ForeignKey.CASCADEandroidx.room.ForeignKey.CASCADE
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(
tableName = "books",
foreignKeys = [ForeignKeyForeignKey(
entity = CategoryCategory::classclass,
parentColumns = arrayOf("shortCode"),
childColumns = arrayOf("categoryShortCode"),
onDelete = CASCADECASCADE
)]
)
data classdata class BookBook(
@PrimaryKey
valval isbn: StringString,
valval title: StringString,
@ColumnInfo(index = truetrue) varvar categoryShortCode: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*) /*()4G**&?&/K
# @Entity ))*//$*))#1 )--4*! @ForeignKey ))*//$*).J)( 
foreignKeysK?# @ForeignKey ))*//$*)#./' .//#- +-$(-4+-*+ -/$ .>
I entity +*$)/./*/#  )/$/4'../#/- +- . )/./# E*) F.$ *!/# *) A/*A
()4- '/$*)
I parentColumns $ )/$8 ./# *'0()J.K$)/# +- )//' /#/- +- . )/
/# +-$(-4& 4
I childColumns $ )/$8 ./# *'0()J.K$)/# #$'/' /#/- +- . )//#
+- )/D.+-$(-4& 4
)/#$.. < Category #..$(+' .$)"' A+-*+ -/4+-$(-4& 4<.* parentColumns
+*$)/./*/#/JshortCodeK<2#$' childColumns +*$)/./*/# *-- .+*)$)"*'0()
$)/# Book JcategoryShortCodeK?
Cascades on Updates and Deletes
)$/$*)<4*0)+' onUpdate ) onDelete +-*+ -/$ .*) @ForeignKey
))*//$*)?# . $)$/ 2#//$*)..#*0' /& )*)/#$. )/$/42# )/#
+- )/*!/# !*- $")& 4- '/$*).#$+$.0+/ *- ' / ?# - - 81
RELATIONS IN ROOM
71
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
+*..$$'$/$ .< )*/ 4 ForeignKey *)./)/.>
Constant
Name
If the Parent Is Updated or Deleted…
NO_ACTION @*)*/#$)"
CASCADE @0+/ *- ' / /# #$'
RESTRICT
@!$'/# +- )/D.0+/ *- ' / *+ -/$*)<0)' ../# - - )*
#$'- )
SET_NULL @. //# !*- $")& 41'0 /* null
SET_DEFAULT @. //# !*- $")& 41'0 /*/# *'0()J.K !0'/1'0
NO_ACTION $./#  !0'/</#*0"# CASCADE 2$'' +*+0'-#*$ !*- onDelete?)
!/<2 0. CASCADE !*- onDelete $)/# Book )/$/4D. @ForeignKey<.*$!/#
Category $. ' / <''*!$/...*$/  Book -*2.-  ' / !-*(/# books /' ?
Retrieving the Related Entities
*-()4/#$)".<*0-) )*$7 - )//#))4*/# -*) 2 #1 . ).*
!-? )$). -/<0+/ < ' / <),0 -4*0- )/$/$ ..2 . 8/?*- 3(+' <
*0- Bookstore #./2* @Insert !0)/$*)./*.1 / "*-$ .)**&.>
@Insert
suspendsuspend funfun save(category: CategoryCategory)
@Insert
suspendsuspend funfun save(varargvararg books: BookBook)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*) /*()4G**&./*- ?&/K
*2 1 -<!-*(+-*+ -/4./)+*$)/<**&.)/ "*-$ .*)'4#1 & 4.<)*/
- ! - ) ./**/# - )/$/$ .?*<4 !0'/<$!2 #1  @Query !0)/$*)/#/- /0-).
Book<2 #1 /* 3 0/ . +-/ @Query !0)/$*)/*'**&0+$/. Category 1$/#
shortCode?)$!2 #1  @Query !0)/$*)/#/- /0-). Category<2 #1 /*
#1 )*/# - @Query !0)/$*)/*- /-$ 1 ''**&...*$/ 2$/#/#/ Category?
*2*-&-*0)/#$.'$($//$*)/*.*(  3/ )/<2 )0. @Relation?
RELATIONS IN ROOM
72
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@Query * .)*/#1 /*- /0-) )/$/$ .?.2 .2 -'$ -<$/)- /0-)*/# -
/#$)".<.0#.) Int - .0'/!-*()""- "/ !0)/$*)?*'*)".**()8"0-
*0/#*2/*(+/# *'0().$)4*0-,0 -4- .0'//*+-*+ -/$ .*!.*( - /0-)/4+ <
**($.#++4?
*<2 ) '- 0./*(/'..!*- @Query - .+*). <.0#./#$.
CategoryAndBooks '..>
data classdata class CategoryAndBooksCategoryAndBooks(
@Embedded
valval category: CategoryCategory,
@Relation(
parentColumn = "shortCode",
entityColumn = "categoryShortCode"
)
valval books: ListList<BookBook>
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*) /*()4G**&./*- ?&/K
40.$)" @Embedded<)4*'0()./#/2 - /0-)!-*(/# @Query /#/)"*$)
Category 2$''"*$)/*/# category +-*+ -/4?
# @Relation ))*//$*).4./#/<$)$/$*)/*+-* ..$)"*0-$- /,0 -4
J!-*( @Query ))*//$*)K<**(.#*0'0/*(/$''4(& . *),0 -4/*
- /-$ 1 - '/  )/$/$ .?$) *0- @Relation $./$ /*+-*+ -/4/#/$.. 
-*0) Book<**(&)*2./#/$/) ./*,0 -4*0- books /' ?# parentColumn
) entityColumn ))*//$*)+-*+ -/$ ./ #**(#*2/*(+/!-*(*0-
$- /,0 -4- .0'//* Book?+ $8''4<**(.#*0'>
I  //# 1'0 *! shortCode !*- Category - /0-) 4/# @Query<)
I 0 -4/# books /' /*8)''-*2.2# - categoryShortCode (/# .
/#/ shortCode 1'0
)/# )0. CategoryAndBooks $) @Query !0)/$*).>
packagepackage com.commonsware.room.misc.onetomanycom.commonsware.room.misc.onetomany
importimport androidx.room.*androidx.room.*
data classdata class CategoryAndBooksCategoryAndBooks(
@Embedded
valval category: CategoryCategory,
@Relation(
RELATIONS IN ROOM
73
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
parentColumn = "shortCode",
entityColumn = "categoryShortCode"
)
valval books: ListList<BookBook>
)
@Dao
interfaceinterface BookstoreBookstore {
@Insert
suspendsuspend funfun save(category: CategoryCategory)
@Insert
suspendsuspend funfun save(varargvararg books: BookBook)
@Transaction
@Query("SELECT * FROM categories")
suspendsuspend funfun loadAll(): ListList<CategoryAndBooksCategoryAndBooks>
@Transaction
@Query("SELECT * FROM categories WHERE shortCode = :shortCode")
suspendsuspend funfun loadByShortCode(shortCode: StringString): CategoryAndBooksCategoryAndBooks
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G*) /*()4G**&./*- ?&/K
# loadAll() ) loadByShortCode() !0)/$*).- /0-) CategoryAndBooks *% /.<
.*2 " // "*-$ .)/# $-..*$/ **&.? 0. **() ./*+ -!*-(
(0'/$+' ,0 -$ .!*-/#$.<2 0. /# @Transaction ))*//$*)/* ).0- /#/**(
* .''*!/#*. ''.$).$ /-)./$*)?
)/# )*/#$)".'$& - / / "*-$ .)**&.>
valval category =
CategoryCategory(shortCode = "stuff", displayName = "Books About Stuff")
valval bookOne = BookBook(
isbn = "035650056X",
title = "Feed",
categoryShortCode = category.shortCode
)
valval bookTwo = BookBook(
isbn = "0451459792",
title = "Dies the Fire",
categoryShortCode = category.shortCode
)
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G*) /*()4G) *)4 ./?&/K
RELATIONS IN ROOM
74
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@.1 /#*. /*/# /. >
underTest.save(category)
underTest.save(bookOne, bookTwo)
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G*) /*()4G) *)4 ./?&/K
@)- /-$ 1 /# ('/ ->
valval all = underTest.loadAll()
assertThat(all, hasSize(equalTo(1)))
assertThat(all[0].category, equalTo(category))
assertThat(
all[0].books,
allOf(hasSize(equalTo(2)), hasElement(bookOne), hasElement(bookTwo))
)
valval loaded = underTest.loadByShortCode(category.shortCode)
assertThat(loaded.category, equalTo(category))
assertThat(
loaded.books,
allOf(hasSize(equalTo(2)), hasElement(bookOne), hasElement(bookTwo))
)
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G*) /*()4G) *)4 ./?&/K
Representing No Relation
*( /$( .<2$/#*) A/*A()4- '/$*).</# (*- *-- /(* '$.E5 -*G*) A/*A
()4F?*- 3(+' <+ -#+. Book #.)*/4 / )..$") /* Category?
*-/#/<.$(+'4(& /# !*- $")& 4+-*+ -/4J ?"?< categoryShortCodeK
)0''' <)' / null - +- . )//# '&*!- '/$*).#$+?
Many-to-Many Relations
)(* '*) A/*A()4- '/$*).4#1$)"/# E()4F.$ J ?"?< BookK#1 
!*- $")& 4&/*$/.*-- .+*)$)"E*) F$/ (J ?"?< CategoryK?
# /-$/$*)'24/*(* '()4A/*A()4- '/$*).$./#-*0"#E%*$)/' F<
2# - -*2.$)/#//' - +- . )//# +$-.*! )/$/$ ./#/- - '/ ?*#)"
/# - '/$*).#$+.<4*0*-- (*1 %*$)/' -*2.?
RELATIONS IN ROOM
75
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#/$.#*2**(#)' .()4A/*A()4- '/$*).<2$/#/# ..$./) *!
@Junction ))*//$*)?
Declaring the Join Table
0++*. /#/2 2)//*.4/#/ Book ) $)(*- /#)*) Category<.0#.
E)-*$-*"-(($)"**&.F)E**&.-$// )4'$)" )F?#/)*2
( )./#/ Book ) Category #1 ()4A/*A()4- '/$*).#$+<.2 ./$''2)/
Category /*#1 ()4 Book *% /.?
0- Category * .)*/) /*#)" >
packagepackage com.commonsware.room.misc.manytomanycom.commonsware.room.misc.manytomany
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "categoriesManyToMany")
data classdata class CategoryCategory(
@PrimaryKey
valval shortCode: StringString,
valval displayName: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G()4/*()4G/ "*-4?&/K
0- Book )*'*)" -) . categoryShortCode<./#/)*)'4(* '*) A/*A
()4- '/$*).#$+>
packagepackage com.commonsware.room.misc.manytomanycom.commonsware.room.misc.manytomany
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "booksManyToMany")
data classdata class BookBook(
@PrimaryKey
valval isbn: StringString,
valval title: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G()4/*()4G**&?&/K
*2 1 -<2 )*2) /#$- )/$/4</*(* '/# %*$)/' J# - '' 
BookCategoryJoinK>
RELATIONS IN ROOM
76
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misc.manytomanycom.commonsware.room.misc.manytomany
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.ForeignKeyandroidx.room.ForeignKey
importimport androidx.room.ForeignKey.CASCADEandroidx.room.ForeignKey.CASCADE
importimport androidx.room.Indexandroidx.room.Index
importimport androidx.room.OnConflictStrategyandroidx.room.OnConflictStrategy
@Entity(
primaryKeys = ["isbn", "shortCode"],
indices = [IndexIndex("isbn"), IndexIndex("shortCode")],
foreignKeys = [
ForeignKeyForeignKey(
entity = BookBook::classclass,
parentColumns = arrayOf("isbn"),
childColumns = arrayOf("isbn"),
onDelete = CASCADECASCADE
), ForeignKeyForeignKey(
entity = CategoryCategory::classclass,
parentColumns = arrayOf("shortCode"),
childColumns = arrayOf("shortCode"),
onDelete = CASCADECASCADE
)
]
)
data classdata class BookCategoryJoinBookCategoryJoin(
valval isbn: StringString,
valval shortCode: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G()4/*()4G**&/ "*-4*$)?&/K
# BookCategoryJoin '..#.*0-& 4.> isbn /*+*$)//* Book ) shortCode /*
+*$)//* Category? ) < # BookCategoryJoin $)./) J*--*2$)$/./' K
- +- . )/.*) - '/$*).#$+ /2 ) Book ) Category?
# @Entity ))*//$*)$.(*- *(+' 3>
I 0. /# primaryKeys +-*+ -/4/*.4/#//# *($)/$*)*! isbn )
shortCode $./# +-$(-4& 4!*-*0-/'
I . /0+ indices *) #*!/#*. *'0().<.2 2$'' ,0 -4$)"/#$.
/' '*//*8)''/ "*-$ .!*-**&*-''**&.!*-/ "*-4
I #1 /2* @ForeignKey ))*//$*).</4$)"/#$.'../* Book )
Category<)0.$)" onDelete = CASCADE /* ).0- /#/2# )2  ' / 
Book *- Category /#/$/.*-- .+*)$)"%*$) )/-4" /. ' /
RELATIONS IN ROOM
77
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Retrieving the Related Entities
) Bookstore</# @Relation ))*//$*)$) CategoryAndBooks #.) 2+-*+ -/4<
associateBy</#/*)/$). @Junction ))*//$*)>
data classdata class CategoryAndBooksCategoryAndBooks(
@Embedded
valval category: CategoryCategory,
@Relation(
parentColumn = "shortCode",
entityColumn = "isbn",
associateBy = JunctionJunction(
value = BookCategoryJoinBookCategoryJoin::classclass,
parentColumn = "shortCode",
entityColumn = "isbn"
)
)
valval books: ListList<BookBook>
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G()4/*()4G**&./*- ?&/K
#$./ # ./# @Relation *0/*0-%*$)/' )#*2/*(+*'0().$)/#
,0 -4D.- .0'/. //**'0().$)/# %*$)/' ?#$.''*2.**(/* ' /*
- /-$ 1 /# **&.!*-/ "*-4?)<$!2 2)/ <2 *0'- / 
BookAndCategories '../#/#)' /# *++*.$/ . <2-++$)" Book )'$./
*!$/...*$/  Category *% /.?
)/# )0. CategoryAndBooks $)*0-!0)/$*).>
@Transaction
@Query("SELECT * FROM categoriesManyToMany")
abstractabstract suspendsuspend funfun loadAll(): ListList<CategoryAndBooksCategoryAndBooks>
@Transaction
@Query("SELECT * FROM categoriesManyToMany WHERE shortCode = :shortCode")
abstractabstract suspendsuspend funfun loadByShortCode(shortCode: StringString): CategoryAndBooksCategoryAndBooks
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G()4/*()4G**&./*- ?&/K
# @Transaction ))*//$*).- /# -  0. 2 2$''2$)0+2$/#(0'/$+'
,0 -$ ./*+*+0'/ *0- CategoryAndBooks *% /.<)**(2$'')*/0/*(/$''4
. /0+/. /-)./$*)!*-0./* ).0- /#//#*. ,0 -$ .''2*-&*7*!/#
.(  $/$*)*!/# /?
RELATIONS IN ROOM
78
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room Entities as DTOs
#$.2*-&.<)$/$.)*//#/$90'//*. /0+?*2 1 -</# - .0'/$)"'.. .-
/$"#/'4*0+' /*/# 0) -'4$)"$/ /' ./-0/0- )"$1 0.'0)&4*% /
(* '?/-$/$*)'*% /A*-$ )/  .$")0.0''4* .)*/$)1*'1 . +-/ *% /.
- +- . )/$)"- '/$*).</# 24 BookCategoryJoin * .<!*- 3(+' ?
)$/$*)<)*/*)'4$.*0-* $/'0)&4/*4<0/$/($"#/) /*#)" $)
/# !0/0- $)24./#/(& $/2*-. ?#/*0' 0 /*#)" .$)**(*-0
/*#)" .$)/# /(* 'J ?"?<$)" Library ) BookLibraryJoinK?
*-.$(+' ++.<4*0($"#/%0./'$1 2$/#/# **% /(* '?*-'-" -++.<$/
(4(& . ). /*/- /**( )/$/$ ..E//-).! -*% /.FJ.K/#/4*0
*)1 -/$)/*+- ! -- *% /(* '?*- 3(+' >
I *0'#1 BookEntity< CategoryEntity< /?/#/(* '*0-/' .
I *0'#1 Book ) Category .*0-*% /(* '<2# - Book )
Category /0''4- ! -/* #*/# -
I - +*.$/*-4*0'0. **(.'*'/.*0- )(+ /2 )/#
**(A./-0/0-  )/$/4'.. .)/# E+0- F*% /(* '- +- . )/ 4
Book ) Category
*0($"#/'- 4 *$)".*( *!/#$..*-/*!(++$)"$)*/# -- .?*-
3(+' <'-" -++.*!/ )#**. /*0. )*% /(* '/#/$.. +-/ !-*(/#
*% /.0. $) . -1$ ''.<./# 24/#  . -1$ $../-0/0- ($"#/
 0))/0-'$)/# '$ )/++?'0.</#  . -1$ ($"#/ (*$8 4/#
. -1 -/ (<)$/(4 0. !0'/*($)$($5 /# $(+/*!/#*. #)" .*)/#
(%*-$/4*!4*0-++'$/$*)* ?
RELATIONS IN ROOM
79
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
The Support Database API
*!-</#$.**&#.+*-/-4 **(. $)")A./4' -$"  /2 )4*0-
* )$/ ?
#)$''4</#/$.)*/0-/ ?
# **(-/$!/.<.0#. androidx.room:room-runtime<#1 /-).$/$1
 + ) )$ .*) androidx.sqlite:sqlite ) androidx.sqlite:sqlite-
framework?**($/. '!/'&./*)./-/$*)-*0)$/ +-*1$ 4
androidx.sqlite:sqlite?#$.**&2$''- ! -/*/#$..E/# .0++*-//. F?
androidx.sqlite:sqlite-framework +-*1$ ./#  !0'/$(+' ( )//$*)*!/#/
./-/$*)<*) /#/2*-&.2$/#/# 0$'/A$)*+4*! SQLiteDatabase J+-/*!/#
E!-( 2*-&FK?# )2 0. RoomDatabase.Builder /*. /0+*0- RoomDatabase<2
- 0.$)"/#*. E!-( 2*-&F'.. .!*-/# /.  ..?
)/#$.#+/ -<2 2$'' 3+'*- $)"- / - /$'2#4/#$..0++*-//.  3$./.
)#*22 )0. $/< 0. 2#$' (*./*!/# /$( 2 2$'' ' /*0. **(A
" ) -/ * /*2*-&2$/#/# /. <.*( /$( .2 ))*/?
“Can’t You See That This is a Facade?”
*()4 1 '*+ -.<$/ E$.2#/$/$.F?)-*$.#$+.2$/#$/
$(+' ( )//$*)<)2 0. $/< $/# -$- /'4*-1$.*( !*-(*!2-++ -'$--4?
*2 1 -<$)/-0/#</# - - ()4$/ $(+' ( )//$*).?!/ -''<$/ $.
'$--4<).*/# - $.)*/#$)"./*++$)"+ *+' !-*(0.$)". +-/ <$) + ) )/
*+4*!$/ !-*(2#/$.$))-*$?1 )$))-*$$/. '!<2#/$/ 4*0" /
 + ).*)2#/ 1$ 4*0-0)*)>
81
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I $7 - )/' 1 '.$)/ "-/ $7 - )/1 -.$*).*!$/
I  1$ ()0!/0- -..*( /$( . - +' /# ./*&$/ 1 -.$*)2$/#
)*/# -
).*<.*( /$( .<2 )  ! >)/#/2 )* /*/#/.0++*-/.
+'0""' $(+' ( )//$*)?# !*''*2$)". /$*).*0/'$) .*(  3(+' .?
Requery
 ,0 -4 $.**(A'$& *% /(++$)"'$--4<*) /#/2*-&.*/#*))-*$)
*)/# - "0'-1?*-+'$)1J*-*/'$)K< ,0 -40. .?*-)-*$<
 ,0 -4$)/ "-/ .2$/#/# .0++*-//. ? 4*)/#/< ,0 -4*7 -. $/.
*2) implementation *!/#/<2-++ -*0)./)'*) *+4*!$/ ?#$.
).0- ./#/4*0- 0.$)"0-- )/1 -.$*)*!$/ < 1 )*)*' - 1$ .?
SQLCipher for Android
) *!/#  ./A&)*2)'/ -)/$1 $/ $(+' ( )//$*).!*-)-*$$. /$/ D.
$+# -!*-)-*$<2#$#*7 -./-).+- )/ )-4+/$*)*!/. *)/ )/.?
.*! 1 -.$*)S?R?O<$+# -!*-)-*$*7 -.)$(+' ( )//$*)*!/# .0++*-/
/. ?#$.''*2.4*0/*0.  )-4+/ /. .!-*(**(? 2$''
3+'*- /#$.$)"- / - /$' '/ -$)/# **&?
SQLDelight
0./. ,0 -4$.)/#/0. ./# .0++*-//. <.*$.  '$"#/?.
2$/# ,0 -4< '$"#/2*-&.*)/# ))-*$?*2 1 -< '$"#/$.
*/'$)G0'/$+'/!*-('$--4<)$/'.*.0++*-/.*/'$)G/$1 !*-$)
$)*2.?*< '$"#/' /.4*02-$/ 4*0-/.  ..* *) )0. $/
$)(0'/$+'  )1$-*)( )/.<) '$"#/!*-)-*$' /.4*00. /# .0++*-/
/. .*4*0)0. /# !-( 2*-&/. < ,0 -4D../)'*) $/ <
*-$+# -!*-)-*$.4*0-/0'/. $(+' ( )//$*)?
When Will We Use This?
# - - /2*-*/ "*-$ .*!. )-$*.2# - /# .0++*-//. *( .
$)/*+'4?
$-./$.2# )4*02)//*0. $7 - )/$/ $(+' ( )//$*)<.0#.2)/$)"/*
0. $+# -!*-)-*$?# )<.+-/*!. //$)"0+4*0- RoomDatabase<4*0)
THE SUPPORT DATABASE API
82
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
+-*1$ $/2$/#/#  /$'.*!#*2/*0. /#/$/ $(+' ( )//$*)<)**(2$''
2*-&2$/#$/?
)$/$*)</# - - */# -+' .$)/# **(2# - /# **(./-/$*).
- &*2))/# .0++*-//. + &./#-*0"#<.0#.>
I # )4*0) /* ($"-/ /. !-*(*) .# (/*)*/# -
I # )4*0) /**)8"0- 4*0-/. $)24. 4*)2#/**(
.0++*-/.<.0#. $- /'4$)1*&$)" PRAGMA .// ( )/.
Configuring Room’s Database Access
#1 0.  RoomDatabase /*. /0+*0-/. )" / ../**0-J.K!*-
2*-&$)"2$/#*0- )/$/$ .?4 !0'/< RoomDatabase 2$''0. /# E!-( 2*-&F
$(+' ( )//$*)*!/# .0++*-//. .?*2 1 ->
I )/ ''$//*0. .*( /#$)" '.
I )" /*)/-*'.+-/*!/# /. . /0+</**)8"0- /# /.
()0''4<- "-' ..*!2#/.0++*-//. $(+' ( )//$*)2 0.
Get a Factory
$/#/# !-( 2*-&D.)-*$$/ <()4 1 '*+ -. ' //*0.
SQLiteOpenHelper ./# $- )/-4+*$)/?#$.#)' .- /$)")0+"-$)"/#
/. $)  )/./-0/0- !.#$*)?*2 1 -< SQLiteOpenHelper $.)*/
- ,0$- ( )/H 1 '*+ -.*0'0. .//$( /#*.*) SQLiteDatabase<.0#.
openOrCreateDatabase()</*2*-&2$/# SQLiteDatabase 2$/#*0/)..*$/ 
SQLiteOpenHelper?
#  ,0$1' )/$)/ -! /* SQLiteOpenHelper $)/# .0++*-//. $.
SupportSQLiteOpenHelper?*2 1 -<2$/#/# .0++*-//. <2*-&$)"2$/#
SupportSQLiteOpenHelper $.0)1*$' ?# /# -4*00. $/<*-**(0. .$/<
somebody . /.0+*) *!/# . ? SupportSQLiteOpenHelper 8''.-*' .$($'-/*/#/
*! SQLiteOpenHelper<+-*1$$)".$)"' +*$)/*!*)/-*'!*-- /$)")0+"-$)"
/. ?
*2 1 -<4*0*)*/- /  SupportSQLiteOpenHelper $- /'44*0-. '!?)./ <
4*0.& SupportSQLiteOpenHelper.Factory /**/#/!*-4*0?#
$(+' ( )//$*)*!/# .0++*-//. .#*0'#1 '../#/$(+' ( )/.
/# SupportSQLiteOpenHelper.Factory $)/ -! >
THE SUPPORT DATABASE API
83
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I #  !0'/**($(+' ( )//$*)$. FrameworkSQLiteOpenHelperFactory<
!-*(/# androidx.sqlite:sqlite-framework -/$!/
I $+# -!*-)-*$#. SupportFactory
I  ,0 -4#. RequerySQLiteOpenHelperFactory
I ).**)
*24*0" /)$)./) *!/#/!/*-4$.0+/*/# $(+' ( )//$*)*!/# .0++*-/
/. ?)/# . *! FrameworkSQLiteOpenHelperFactory<4*0%0./- / )
$)./) 1$)*A+-( / -*)./-0/*-?$+# -!*-)-*$*7 -./#-
SupportFactory *)./-0/*-.<2# - /# +..+#-. $.(*)"/# 1-$*0.
+-( / -.?
 "-' ..<*) 24*-)*/# -<4*02$'') /*" /)$)./) *!!/*-4?
*0)0. /# !/*-4$- /'4<4+..$)"''*!**(?/# -/$( .<4*02$''2)//*
0. **(<0/#1 **(0. /#$..0++*-//. $(+' ( )//$*)?
*-/#/<'' openHelperFactory() *)/# RoomDatabase.Builder .+-/*!. //$)"
$/0+>
valval db = RoomRoom.databaseBuilder(ctxt, StuffDatabaseStuffDatabase.classclass, DB_NAMEDB_NAME)
.openHelperFactory(SupportFactorySupportFactory(passphrase))
.build()
 - <2 - #1$)"**(0. SupportFactory !-*($+# -!*-)-*$?**(
2$'')*20. $+# -!*-)-*$<1$ SupportFactory<!*-''*!$/./0'
/. G?
2$'' 3($) $+# -!*-)-*$<)$/.0. 2$/#**(<(*- '/ -$)/#
**&?
Add a Callback
 "-' ..*!2# /# -2 0. openHelperFactory() *-)*/<2 )'.*''
addCallback() *)/# RoomDatabase.Builder /*.0++'4 RoomDatabase.Callback
/*0. ?#$.''&)" /*)/-*'//2*+*$)/.>
I # )/# /. 8' $.- / <1$) onCreate() !0)/$*)*)/#
''&
I # )/# /. 8' $.*+ ) <1$) onOpen() !0)/$*)*)/# ''&
THE SUPPORT DATABASE API
84
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
) #. <4*0" / SupportSQLiteDatabase *% //*0. !*-()$+0'/$)"/#
/. ?**($/. '!(4)*/ *(+' / '4- 4!*-0. H+-/$0'-'4$)/#
onCreate() ''&H2#$#$.2#44*0- )*/+.. 4*0- RoomDatabase
.0'..?)./ <4*0#1 /*2*-&2$/#/# /. 0.$)"/# .0++*-//.
$- /'4?
2$''.  3(+' .*!/#$.<$)/# *)/ 3/*!-0))$)".*( PRAGMA .// ( )/.<
'/ -$)/# **&?
THE SUPPORT DATABASE API
85
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Database Migrations
# )4*08-./.#$+4*0-++<4*0/#$)&/#/4*0-/. .# ($. 0/$!0'<
/-0 2*-&*!-/?
# )<4*02& 0+/# ) 3/(*-)$)")- '$5 /#/4*0) /*(& #)" ./*
/#/.# (?
0-$)"$)$/$' 1 '*+( )/H)!*-.$''4'$//' **& 3(+' .H4*0%0./"*$))
(& #)" ./*4*0- )/$/$ .<)**(2$''- 0$'4*0-/. !*-4*0?
*2 1 -<$/* ..*4-*++$)"''*!4*0- 3$./$)"/' .</&$)"''/# /2$/#$/?
) 1 '*+( )/</#/(4)*/ .*?) production@2 ''<0. -." /.*( 2#/
$--$// 2# )4*0'*. /# $-/?
)/#/$.2# - ($"-/$*).*( $)/*+'4?
What’s a Migration?
.( )/$*) $) /# +-  $)"#+/ -<2$/#/-$/$*)')-*$$/
 1 '*+( )/<2 /4+$''40. SQLiteOpenHelper?#$.0/$'$/4'..()" .
SQLiteDatabase !*-0.)- .. ./2*& 4+-*' (.>
P? #/#++ ).2# )*0-++8-./-0).*) 1$ H*-!/ -/# 0. -#.
' - *0-++D./H)2 #1 )*/. /''C
Q? #/#++ ).2# )2 ) /*(*$!4/# /. .# (!-*(2#/$/
2./*.*( ) 2./-0/0- C
SQLiteOpenHelper *(+'$.# ./#$.4''$)" onCreate() ) onUpgrade()
''&.<2# - 2 *0'$(+' ( )//# '*"$/*- / /# /' .)%0.//# (
./# .# (.#)" ?
87
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#$' onCreate() 2*-& - .*)'42 ''< onUpgrade() *0'-+$'4"-*2*0/*!
*)/-*'?*)"A'$1 ++.($"#/#1 *5 ).*!$7 - )/.# (.< 1*'1$)"*1 -/$( ?
 0. 0. -.- )*/!*- /*/& *)++0+/ .<*0-++.) /* ' /*
/-).$/$*)!-*()4+-$*-.# (/*/# '/ ./A)A"- / ./*) ?#$.( )//#/
onUpgrade() 2*0') /*$ )/$!4 3/'42#/$/.*!* - )  /*($"-/
/# /. !-*(/# *'/*/# ) 21 -.$*)<)/#$.*0'" /0)2$ '4?
**(- .. ./#$..*( 2#//#-*0"#/# Migration '..?*0- / .0'.. .
*! Migration H/4+$''4.*/'$) object $(+' ( )//$*).H/#/#)' /#
*)1 -.$*)!-*(.*( *' -.# (/*) 2 -*) ?*0+..0)#*! Migration
$)./) ./***(<- +- . )/$)"$7 - )/+$-A2$. .# (0+"- +/#.?**(
/# ) / -($) .2#$#*) J.K) /* 0. /)4+*$)/$)/$( </*0+/ /#
.# (!-*(2#/ 1 -$/2./*2#/ 1 -$/) ./* ?
When Do We Migrate?
)*0- RoomDatabase .0'..<2 #1  @Database ))*//$*)?) *!/#
+-*+ -/$ .$. version?#$.2*-&.'$& /# 1 -.$*)* /#/2 2*0'+..$)/*/#
SQLiteOpenHelper *)./-0/*-?/$.(*)*/*)$''4$)- .$)"$)/ " -<2$/##$"# -
)0( -.$)$/$)") 2 -.# (.?# version $)/# * - +- . )/./# .# (
1 -.$*)/#//#$.* $. 3+ /$)"?
) 4*0-++.#$+.<)4/$( 4*0#)" 4*0-.# (H(*./'4$)/# !*-(*!
(*$!4$)" )/$/4'.. .H4*0) /*$)- ( )//#/ version )- / 
Migration /#/&)*2.#*2/**)1 -/!-*(/# +-$*-1 -.$*)/*/#$.) 2*) ?
*/ /#//# - $.)*- ,0$- ( )//#/4*0$)- ( )//# version 4P</#*0"#/#/
$.*((*)*)1 )/$*)?!0.$)"/ A. !*-(/'$& YYYYMMDD J ?"?< 20170627K
(& .4*0-'$!  .$ -<4*0- 2 '*( /**.*B
But First, a Word About Exporting Schemas
) *!/# .$ A 7 /.*!0.$)"**($./#/4*0*)*/2-$/ 4*0-*2).# (!*-
/# /. ?**(" ) -/ .$/<. *)4*0- )/$/4 8)$/$*).?0-$)"/#
*-$)-4*0-. *!+-*"-(($)"</#$.$.+ -! /'48) ).1 .4*0/$( ) 7*-/?
*2 1 -<2# )$/*( ./*($"-/$*).<)*22 #1 +-*' (? ))*/- /
* /*($"-/ !-*()*'/*) 2.# (2$/#*0/&)*2$)"2#//#*. .# (.
- ?)2#$' .# ($)!*-(/$*)$.& $)/*.*( * " ) -/ 4**(D.
))*//$*)+-* ..*-</#/$.*)'4!*-/# 0-- )/1 -.$*)*!4*0- )/$/4'.. .J)<
DATABASE MIGRATIONS
88
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# ) <4*0-0-- )/.# (K<)*/!*-)4#$./*-$'*) .?
*-/0)/ '4<**(*7 -..*( /#$)"/#/# '+.$/> 3+*-/ .# (.?*0)
/ #**(D.))*//$*)+-* ..*-/*)*/*)'4" ) -/ 1* 0/'.*" ) -/
*0( )/ .-$$)"/# .# (?*- *1 -<$/2$''*/#/!*- #.# (
1 -.$*)<.1$)"/# (/*1 -.$*)A.+ $88' .?!4*0#*'*)/*/# . 8' .H!*-
3(+' <$!4*0.1 /# ($)$/*-.*( */# -!*-(*!1 -.$*)*)/-*'H4*02$''
#1 #$./*-4*!4*0-.# ())0. /#/$)!*-(/$*)/*2-$/ 4*0-($"-/$*).?
*2 1 -</# - '- .*)!*-/#*.  3+*-/ .# (.$./*# '+2$/#/ ./$)"4*0-
($"-/$*).?.- .0'/</# !*-(/$.)*/ .$") !*- 1 '*+ -./*- ?
*. //#$.0+<$)/# defaultConfig '*.0- *!4*0-(*0' D. build.gradle 8' <4*0
) javaCompileOptions '*.0- >
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
J!-*( $"-/$*)G0$'?"-' K
#$./ # .**(/*.1 4*0-.# (.$) schemas/ $- /*-4*7*!/# (*0'
-**/$- /*-4?)+-$)$+' <4*0*0'./*- /# ( '. 2# - 4#**.$)"$7 - )/
1'0 !*-/# room.schemaLocation -"0( )/?
# ) 3//$( 4*0J- AK0$'4*0-+-*% /</#/$- /*-42$'' - / ?
0$- /*-$ .2$/#/# !0''4A,0'$8 '..)( .*!4*0- RoomDatabase '.. .2$''
"*$).$ /# - <)$).$  #*!/#*. 2$'' 8' )( !/ -4*0-.# (
1 -.$*)J ?"?< 1.jsonK>
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "051fc3ca1ecb3344055fd77365a9bf8e",
"entities": [
{
"tableName": "notes",
DATABASE MIGRATIONS
89
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT
NULL, `text` TEXT NOT NULL, `version` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": truetrue
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": truetrue
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": truetrue
},
{
"fieldPath": "version",
"columnName": "version",
"affinity": "INTEGER",
"notNull": truetrue
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": falsefalse
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42,
'051fc3ca1ecb3344055fd77365a9bf8e')"
]
}
}
J!-*( $"-/$*)G.# (.G*(?*((*).2- ?-**(?($"-/$*)?*/ /. GP?%.*)K
# +-*+ -/$ ./#/2$''(// -/*4*02$'' /# createSql *) .?# - -
*) ./#/- / 4*0-/' .)*/# -./#/- / 4*0-$) 3 .?#$.$.!$-'4)*-('
< 3 +//#/>
I # /' )( $.$)% / /-0)/$( <- +'$)"/# ${TABLE_NAME}
+' #*' -
I &/$&.- 2-++ -*0)*'0())( .
DATABASE MIGRATIONS
90
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Writing Migrations
Migration $/. '!#.*)'4*) - ,0$- ( /#*> migrate()?*0- "$1 )
SupportSQLiteDatabase<!-*(/# .0++*-//. *1 - $) /# +-  $)"
#+/ -?*0)0. /# SupportSQLiteDatabase /* 3 0/ 2#/ 1 -
.// ( )/.4*0) /*#)" /# /. .# (/*2#/4*0) ?
# Migration *)./-0/*-/& ./2*+-( / -.>/# *'.# (1 -.$*))0( -
)/# ) 2.# (1 -.$*))0( -?*02$''*)'4 1 -) *) $)./) *!
Migration !*-"$1 ).# (1 -.$*)+$-<)0.0''4/# migrate()
$(+' ( )//$*)!*-/#/.# (1 -.$*)+$-2$'' 0)$,0 ? ) </# /4+$'
+// -)$./*$(+' ( )/ # Migration .*/'$) object<2# - 4*0)+-*1$
/# migrate() !0)/$*)/*0. !*-($"-/$)"/# .# ( /2 )/#/+-/$0'-+$-
*!.# (1 -.$*).?
* / -($) 2#/) ./* *) <4*0) /* 3($) /#/.# ()
 / -($) 2#/$.$7 - )/ /2 )/# *')/# ) 2?*( 4<2 (4" /
.*( /**'./*# '+2$/#/#$.?*-)*2<4*0- '-" '4./0&E 4 ''$)"F/# ?*0
)/# )-!//# ALTER TABLE *-*/# -.// ( )/.)  ..-4/*#)" /# .# (<
(0#.4*0($"#/#1 *) $) onUpgrade() *! SQLiteOpenHelper?
*- 3(+' < /# Migration (*0' *! /# **&D.+-$(-4.(+' +-*% / #.
NoteDatabase &$)/*/# NoteBasics (*0' /#/2 .2 -'$ -$)/# **&?)
$7 - ) $./#/2 #1  MIGRATION_1_2 *% //#/$(+' ( )/. Migration>
@VisibleForTesting
internalinternal valval MIGRATION_1_2 = objectobject : MigrationMigration(1, 2) {
overrideoverride funfun migrate(db: SupportSQLiteDatabaseSupportSQLiteDatabase) {
db.execSQL("ALTER TABLE notes ADD COLUMN andNowForSomethingCompletelyDifferent TEXT")
}
}
J!-*( $"-/$*)G.-G($)G%1G*(G*((*).2- G-**(G($"-/$*)G*/ /. ?&/K
0- migrate() !0)/$*) 3 0/ .) ALTER TABLE .// ( )/<0.$)" execSQL() *)
/# .0++'$  SupportSQLiteDatabase *% /?# MIGRATION_1_2 *% /.0++'$ . 1
) 2 ./# .# (1 -.$*)+$-/*/# Migration *)./-0/*-</*$ )/$!42#/
1 -.$*)+$-/#$. migrate() 2$''#)' ?
Employing Migrations
$(+'4- /$)" Migration .) object .*( 2# - $.)  ..-40/)*/.09$ )/
DATABASE MIGRATIONS
91
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/*#1 **(&)*2*0/+ -!*-($)"/# ($"-/$*)?)./ <4*0) /*0. /#
addMigrations() ( /#**) RoomDatabase.Builder /*/ #**(*0/4*0-
Migration *% /.? addMigrations()  +/.1--".<).*4*0)+..$)*) *-
. 1 -' Migration *% /..)  ?
packagepackage com.commonsware.room.migrationcom.commonsware.room.migration
importimport android.content.Contextandroid.content.Context
importimport androidx.annotation.VisibleForTestingandroidx.annotation.VisibleForTesting
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.migration.Migrationandroidx.room.migration.Migration
importimport androidx.sqlite.db.SupportSQLiteDatabaseandroidx.sqlite.db.SupportSQLiteDatabase
@Database(entities = [NoteEntityNoteEntity::classclass], version = 2)
abstractabstract classclass NoteDatabaseNoteDatabase : RoomDatabaseRoomDatabase() {
companioncompanion objectobject {
funfun newTestDatabase(context: ContextContext) = RoomRoom.inMemoryDatabaseBuilder(
context,
NoteDatabaseNoteDatabase::classclass.java
)
.addMigrations(MIGRATION_1_2MIGRATION_1_2)
.build()
}
abstractabstract funfun notes(): NoteStoreNoteStore
}
@VisibleForTesting
internalinternal valval MIGRATION_1_2 = objectobject : MigrationMigration(1, 2) {
overrideoverride funfun migrate(db: SupportSQLiteDatabaseSupportSQLiteDatabase) {
db.execSQL("ALTER TABLE notes ADD COLUMN andNowForSomethingCompletelyDifferent TEXT")
}
}
J!-*( $"-/$*)G.-G($)G%1G*(G*((*).2- G-**(G($"-/$*)G*/ /. ?&/K
#$.1 -.$*)*! NoteDatabase #. companion object 2$/# newTestDatabase()
!0)/$*)/#/*0-/ ./.)0. ?.+-/*!0$'$)"/# NoteDatabase $)./) <2
0. addMigrations(MIGRATION_1_2) /*/ #**(*0/*0- Migration?
'.*<)*/ /#//# version $)/# @Database ))*//$*)$. 2?)/# *-4</#$.(*0'
 (*)./-/ .)++/#/#. )(*$8 !-*($/.*-$"$)'?# * ./-/ 2$/#
/# NoteBasics $/$*)*! NoteEntity>
packagepackage com.commonsware.room.notescom.commonsware.room.notes
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "notes")
DATABASE MIGRATIONS
92
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
data classdata class NoteEntityNoteEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval text: StringString,
valval version: IntInt
)
J!-*( */ .$.G.-G($)G%1G*(G*((*).2- G-**(G)*/ .G*/ )/$/4?&/K
# Migration (*0'  ) 2)0''' +-*+ -/4/* NoteEntity>
packagepackage com.commonsware.room.migrationcom.commonsware.room.migration
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "notes")
data classdata class NoteEntityNoteEntity(
@PrimaryKey valval id: StringString,
valval title: StringString,
valval text: StringString,
valval version: IntInt,
valval andNowForSomethingCompletelyDifferent: StringString?
)
J!-*( $"-/$*)G.-G($)G%1G*(G*((*).2- G-**(G($"-/$*)G*/ )/$/4?&/K
#/+-*+ -/4$./# *'0()/#/2 - $)"$) MIGRATION_1_2</*#)' 0. -
/#/# )0.$)"*0-++2$/#.# (1 -.$*) 1 ))*20+"- ./*) 2 -
*+4*!*0-++/#/0. ..# (1 -.$*) 2?
)+-$)$+' < MIGRATION_1_2 *0' private /* NoteDatabase?*2 1 -<2 2)//*
 ' /* / ./*0-($"-/$*)<.*2 #1 $/(-& . internal $)./ <2$/#/#
@VisibleForTesting ))*//$*)/*# '+$.*0-" 0) 3+ / 0. ?
How Room Applies Migrations
# )4*0- / 4*0- RoomDatabase $)./) 1$/# MigrationA )#)  Builder<
**(2$''0. SQLiteOpenHelper . ()/$./*. $!/# .# (1 -.$*)$)/#
3$./$)"/. $.*' -/#)/# .# (1 -.$*)/#/4*0 '- $)4*0-
@Database ))*//$*)?!$/$.<**(2$''/-4/*8).0$/' Migration /*0. <
!''$)"&/*-*++$)"''*!4*0-/' .)- 0$'$)"/# (!-*(.-/#<.
#++ ).0-$)"*-$)-4 1 '*+( )/?
DATABASE MIGRATIONS
93
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
0#*!/# /$( </# .# (2$''%0(+!-*(*) 1 -.$*)/*/# ) 3/?!4*0-
0.$)".$(+' )0( -$)".# ( ./-/$)"/P</# .# (2$''/# )(*1 /*Q</# )
R</# )S<).**)<!*-"$1 ) 1$ ? ) <4*0-+-$(-4 Migration *% /.2$''
 *) ./#/$(+' ( )//# . $)- ( )/'($"-/$*).?
*2 1 -<.*( 0. -($"#/#1 .&$++ .*( ++0+/ .<.*4*0) /*.&$+
.# (1 -.$*).+-/*!)0+"- J ?"?<"*!-*(.# (1 -.$*)P/*.# (
1 -.$*)RK?**($..(-/ )*0"#/*8)#$)*! Migration *% /./*0. <).*
$!4*0#1 Migration *% /.!*- #$)- ( )/'.# (#)" <**()
#)' )4*($)/$*)*!#)" .?*- 3(+' </*"*!-*(P/*R<**(($"#/8-./
0. 4*0- (1,2) ($"-/$*)</# )/# (2,3) ($"-/$*)?
*( /$( .</#*0"#</#$.)' /*0))  ..-42*-&?0++*. $).# (1 -.$*)Q<
4*0- / 0)#*!) 2/' .)./07@/# )- 1 -/ /#*. #)" .$).# (
1 -.$*)R?40.$)"/# $)- ( )/'($"-/$*).<**(2$''- / /#*. /' .)
/# )/0-)-*0))-*+/# (-$"#/24?
*2 1 -<'' '.  $)" ,0'<**(2$''/-4/*0. /# .#*-/ ./+*..$' #$)?
 ) <4*0)- / $/$*)' Migration *% /.2# - ++-*+-$/ /*./- ('$)
+-/$0'-0+"- .?*0*0'- /  (1,3) ($"-/$*)/#/4+.. ./# *.*' /
.# (1 -.$*)Q<!*- 3(+' ?#$.$.*+/$*)'0/(4+-*1 0. !0'!-*(/$( /*
/$( ?
Testing Migrations
/2*0' )$ $!4*0-($"-/$*).2*-& ?. -.<$)+-/$0'-<++- $/ 2*-&$)"
* @*-<+ -#+.(*- *-- /'4</# 4" /-/# -)"-42$/#)*)A2*-&$)"* ?
 ) <4*0($"#/2)//*/ .//# ($"-/$*).?
#$." /.$//-$&4</#*0"#?# * A" ) -/ **('.. .-  3+ /$)"/#
'/ ./A)A"- / ./.# (1 -.$*)<.*4*0))*/0. 4*0-!*-/ ./$)"*' -
.# (.? .$ .< RoomDatabase.Builder 2)/./*. /0+4*0-/. 2$/#/#/
'/ ./A)A"- / ./.# (0/*(/$''4?
*-/0)/ '4<**(.#$+.2$/#.*( / ./$)"* /*# '+4*0/ ./4*0-.# (.$)
$.*'/$*)@/#*0"#4*04+..(*./*!**(/**/#/?
DATABASE MIGRATIONS
94
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Adding the Artifact
#$./ ./$)"* $.$). +-/ androidx.room:room-testing -/$!/<*) /#/4*0
)1$ androidTestCompile /*+0/$)4*0-$)./-0( )//$*)/ ./.0/' 1 *0/
*!4*0-+-*0/$*)* >
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.arch.core:core-runtime:2.1.0"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "androidx.room:room-testing:$room_version"
androidTestImplementation "com.natpryce:hamkrest:1.7.0.0"
}
J!-*( $"-/$*)G0$'?"-' K
Adding the Schemas
 ( ( -/#*.  3+*-/ .# (.C#$' 2 0. /# (!*-# '+$)"0.2-$/ /#
($"-/$*).</# $-+-$(-40. $.!*-/#$./ ./$)".0++*-/* ?
4 !0'/</#*. .# (.- ./*- *0/.$ *!)4/#$)"/#/"* .$)/*4*0-++?
!/ -''<4*0*)*/) /#*. 8' .'0// -$)"0+4*0-+-*0/$*)0$'.?
*2 1 -</#$.'.*( )./#//#*. .# (.- )*/1$'' /*4*0-/ ./* <4
 !0'/?
*2 1 -<2 )83/#/<4$)"/#*. .# (./*/# assets/ 0. $)/#
androidTest .*0- . /<4#1$)"/#$.'*.0- $)4*0- android '*.0- *!4*0-
(*0' D. build.gradle 8' >
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
J!-*( $"-/$*)G0$'?"-' K
 - < "$projectDir/schemas".toString() $./# .( 1'0 /#/2 0. !*-/#
room.schemaLocation ))*//$*)+-* ..*--"0( )/?#$..)$++ // ''.-' /*
$)'0 /# *)/ )/.*!/#/ schemas/ $- /*-4.+-/*!*0- assets/?
DATABASE MIGRATIONS
95
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# - .0'/$./#/*0-$)./-0( )//$*)/ ./2$''#1 /#*. $- /*-$ .)( 
!/ -*0- RoomDatabase '.. .J ?"?<
com.commonsware.room.migration.NoteDatabase/K$)/# -**/*! assets/?!4*0
#1 * /#/0. . assets/<(& .0- /#/4*0- /&$)"./ +./*$")*- /# .
3/-$- /*-$ .?
Creating a MigrationTestHelperMigrationTestHelper
# / ./$)".0++*-/*( .$)/# !*-(*! MigrationTestHelper /#/4*0)
(+'*4$)4*0-$)./-0( )//$*)/ ./.?
MigrationTestHelper $.)$/S-0' <2#$#4*0/*4*0-/ ./. '..1$/#
@Rule ))*//$*)>
@getget:RuleRule
valval migrationTestHelper = MigrationTestHelperMigrationTestHelper(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation(),
NoteDatabaseNoteDatabase::classclass.java.canonicalName
)
J!-*( $"-/$*)G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($"-/$*)G$"-/$*) ./?&/K
# MigrationTestHelper *)./-0/*-/& ./2*+-( / -.<*/#*!2#$#- $/
0)0.0'?
$-./<$//& .) Instrumentation *% /? 0. /#*. $)*0-/ ./* <0/$/$.--
/#/2 +../# (.+-( / -?*0" /4*0- Instrumentation 4''$)"
getInstrumentation() *)/# InstrumentationRegistry?
# )<$//& .2#/++ -./* /# !0''4A,0'$8 '..)( *!/# RoomDatabase
2#*. ($"-/$*).2 2$.#/*/ ./? #)$''4.+ &$)"</#$.$./0''4/# - '/$1
+/#<$).$ *! assets/<2# - /# .# (8' .- !*-/#$.+-/$0'-
RoomDatabase?$1 )/# *1 *)8"0-/$*)< #/. D..# (.- +0/$)/*
$- /*-4)( !/ -/# !0''4A,0'$8 '..)( *!/# RoomDatabase<2#$#$.
2#4/#$.2*-&.?*2 1 -<$!4*0#)" /# *)8"0-/$*)/*+0//# .# (.
.*( 2# -  '. $) assets/<4*02*0') /*(*$!4/#$.+-( / -/*(/#?
Creating a Database for a Schema Version
# - - /2*($)( /#*.*) MigrationTestHelper /#/2 2$''0. $)/ ./$)"?
) $. createDatabase()?#$.- / ./# /. <..+ $8/. 8' <!*-
.+ $8.# (1 -.$*)@$)'0$)")4*!*0-#$./*-$'*) .!*0)$)/#*. .# (
DATABASE MIGRATIONS
96
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
8' .? - <2 .&/# # '+ -/*- / /. )(  DB_NAME !*-.# (
1 -.$*)P>
valval initialDb = migrationTestHelper.createDatabase(DB_NAMEDB_NAME, 1)
J!-*( $"-/$*)G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($"-/$*)G$"-/$*) ./?&/K
#$."$1 .4*0 SupportSQLiteDatabase<)*/**(/. J ?"?< NoteDatabaseK?
#/$. 0. 4*0(4 0.$)"#$./*-$'.# (1 -.$*)<)/# **(A
" ) -/ * *)'4 3$./.!*-/# (*./-  )/.# (1 -.$*)?
.+-/*!/ ./$)"($"-/$*)<4*02$'') /*.*( .(+' //*/#
/. <0.$)"2#/ 1 -.# (4*0.& /* 0. <.*/#/4*0)*)8-(/#/
/# ($"-/$*)2*-& . 3+ / )$)*/2- &/#  3$./$)"/?#$.* 2$''
)*/ 1 -4**(A$.#<0/(*- '$& '..$$/ )-*$+-*"-(($)">
valval initialDb = migrationTestHelper.createDatabase(DB_NAMEDB_NAME, 1)
initialDb.execSQL(
"INSERT INTO notes (id, title, text, version) VALUES (?, ?, ?, ?)",
arrayOf(TEST_IDTEST_ID, TEST_TITLETEST_TITLE, TEST_TEXTTEST_TEXT, TEST_VERSIONTEST_VERSION)
)
initialDb.query("SELECT COUNT(*) FROM notes").use {
assertThat(it.count, equalTo(1))
it.moveToFirst()
assertThat(it.getInt(0), equalTo(1))
}
initialDb.close()
J!-*( $"-/$*)G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($"-/$*)G$"-/$*) ./?&/K
Testing a Migration
# */# -( /#**!)*/ *) MigrationTestHelper $.
runMigrationsAndValidate()?!/ -4*0#1 . /0+/. $)$/../-/$)"
*)$/$*).1$ createDatabase() )*+ -/$*).<
runMigrationsAndValidate() 2$''($"-/ /#//. !-*($/.*-$"$)'.# (
1 -.$*)/*/# *) /#/4*0.+ $!4>
valval db = migrationTestHelper.runMigrationsAndValidate(
DB_NAMEDB_NAME,
2,
DATABASE MIGRATIONS
97
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
truetrue,
MIGRATION_1_2MIGRATION_1_2
)
J!-*( $"-/$*)G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($"-/$*)G$"-/$*) ./?&/K
*0) /*.0++'4/# .( /. )( JDB_NAMEK<#$"# -.# (1 -.$*)J2K<
)/# .+ $8 Migration /#/4*02)//*0. JMIGRATION_1_2K?
*/*)'4* ./#$.( /#*+ -!*-(/# ($"-/$*)<0/$/1'$/ ./# - .0'/$)"
.# ("$)./2#//#  )/$/$ .#1 . /0+!*-/#/.# (1 -.$*)<. *)/#
.# (8' .?!/# - $..*( /#$)"2-*)"H4*0-($"-/$*)!*-"*/) 2'4A
 *'0()<!*- 3(+' H4*0-/ ./2$''!$'2$/#).. -/$*)1$*'/$*)?#
true +-( / -.#*2)*1  / -($) .2# /# -/#$..# (1'$/$*)2$''
# & !*-0)A-*++ /' .? true ( )./#/$!4*0#1 0))  ..-4/' .$)
/# /. </# / ./!$'.= false ( )./#/0))  ..-4/' .- 8) )2$''
$")*- ?
*2 1 -<'' MigrationTestHelper )*$.*)8-(/#/4*0. /0+/# ) 2
.# (+-*+ -'4)"$1 4*0 SupportSQLiteDatabase - +- . )/$)"/# ($"-/
/. ?/))*/ / -($) 2# /# -/# /$.)4"**!/ -/# ($"-/$*)?
#/4*02*0') /*/ ./4*0-. '!>
valval db = migrationTestHelper.runMigrationsAndValidate(
DB_NAMEDB_NAME,
2,
truetrue,
MIGRATION_1_2MIGRATION_1_2
)
db.query("SELECT id, title, text, version, andNowForSomethingCompletelyDifferent FROM notes")
.use {
assertThat(it.count, equalTo(1))
it.moveToFirst()
assertThat(it.getInt(0), equalTo(TEST_IDTEST_ID))
assertThat(it.getString(1), equalTo(TEST_TITLETEST_TITLE))
assertThat(it.getString(2), equalTo(TEST_TEXTTEST_TEXT))
assertThat(it.getInt(3), equalTo(TEST_VERSIONTEST_VERSION))
assertThat(it.getString(4), absent())
}
J!-*( $"-/$*)G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($"-/$*)G$"-/$*) ./?&/K
)()4. .</# - $.'$//' /*/ ./<+-/$0'-'4$!4*0- %0./. //$)"0+ (+/4
/' ..2 - *$)"$)/#$.($"-/$*)?*2 1 -<$!4*0#*(+' 3/' #)" <
+ -#+.- ,0$-$)"/ (+/' ).// ( )/.'$& INSERT INTO ... SELECT FROM
...<4*0*0'2-$/ / ./* /#/*)8-(./# /$.?*2 1 -<..#*2)
*1 <4*0))*/0. /# **(!*-/#$. $/# -?)./ <4*02$''0. /#
DATABASE MIGRATIONS
98
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
SupportSQLiteDatabase )2*-&2$/#/# /' .E/# *'A!.#$*) 24F<0.$)"
query() ) Cursor ).$($'-*)./-0/.?
DATABASE MIGRATIONS
99
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Intermediate Room
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Polymorphic Entities
1)*/'$)+-*"-(( -.- 0. /*+*'4(*-+#$.(<2# - 4*0)/- /*% /.
. $)"*!/# .( /4+ <2# )$)/-0/#/# $-*)- / /4+ .$7 -?#$.*0'
. *)*((*)$)/ -! *-*((*). '..Jabstract *-*/# -2$. K?
#*. 0. /*+0//$)"/$)/*/. .- 0. /*/# !//#/
+*'4(*-+#$.()- '/$*)'/. *)*/2*-&/*" /# -)/0-''4?#$.$.%0./
E*) *!/#*. /#$)".F/#/ 1 '*+ -.#1 /* '2$/#<.+-/*!E*% /A- '/$*)'
$(+ ) ($.(/#F?
# - - ! 2./-/ "$ .!*- '$)"2$/#+*'4(*-+#$- '/$*).$)- '/$*)'
/. .?#$.#+/ -*0/'$) ./2**!/# (<2$/#) 4 /*2-.#*2/# 4)
$(+' ( )/ 2$/#**(?
Polymorphism With Separate Tables
) ++-*#0. .. +-/ /' !*-$)./) .*! #*)- / /4+ ?*<!*-
3(+' $!2 #1  CommentEntity '..) LinkEntity '..<)/# 4*/#
$(+' ( )/*((*) Note $)/ -! <2 2$)0+2$/# $/ /' .!*-
CommentEntity ) LinkEntity?#$.& +./# /. ./-0/0- .$(+' <.2 ./$''
#1 P>P- '/$*).#$+ /2 )*)- / '..)/' ?*2 1 -<$/( )./#/)4
+ -.$./ ) * /#/ '.2$/# Note *% /.) ./*#)' /# !//#/ Note $.
./*- $7 - )/'4!*-$7 - )/ Note $(+' ( )//$*).<)/#/)*(+' 3$/4?
# /# MiscSamples (*0' *! /# **&D.+-$(-4.(+' +-*% / #. poly .0A
+&" 2$/#'.. ./#/$(+' ( )//#$../-/ "4?
. +$/ $)/# +-  $)"+-"-+#<2 #1  Note $)/ -! >
103
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misc.polycom.commonsware.room.misc.poly
interfaceinterface NoteNote {
valval displayText: CharSequenceCharSequence
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4G*/ ?&/K
'.*#1 CommentEntity ) LinkEntity '.. ./#/$(+' ( )//#/$)/ -!
)#1 .'$"#/'4$7 - )/*)/ )/.>
packagepackage com.commonsware.room.misc.polycom.commonsware.room.misc.poly
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "comments")
data classdata class CommentEntityCommentEntity(
@PrimaryKey
valval id: LongLong,
valval text: StringString
) : NoteNote {
overrideoverride valval displayText: CharSequenceCharSequence
getget() = text
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4G*(( )/)/$/4?&/K
packagepackage com.commonsware.room.misc.polycom.commonsware.room.misc.poly
importimport androidx.core.text.HtmlCompatandroidx.core.text.HtmlCompat
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "links")
data classdata class LinkEntityLinkEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong,
valval title: StringString,
valval url: StringString
) : NoteNote {
overrideoverride valval displayText: CharSequenceCharSequence
getget() = HtmlCompatHtmlCompat.fromHtml(
"""<a href="$url">$title</a>""",
HtmlCompatHtmlCompat.FROM_HTML_MODE_COMPACTFROM_HTML_MODE_COMPACT
)
}
POLYMORPHIC ENTITIES
104
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4G$)&)/$/4?&/K
'' Note 2)/.$. displayName CharSequence 2$/#$.+'4- +- . )//$*)*!/#
)*/ ? Comment %0./#*'..*( / 3/JtextK<.*/# displayName $.%0.//# text? Link
#.)/$/' <.*/# displayName $.'$&' - )$/$*)*!/#/'$)&<# -
" ) -/ 4.)$++ /*!) HtmlCompat?(*-  9$ )/<0/' ..- ' <
++-*#2*0' /*0.  ClickableSpan 2$/# SpannableStringBuilder /*
- / /#$.'$&' '$)&?
PolyStore $.*0-!*-()$+0'/$)"/# . *% /.?#/) ./*#1 !0)/$*).
!*-2*-&$)"2$/#*0-/2* )/$/4'.. .>
@Query("SELECT * FROM comments")
funfun allComments(): ListList<CommentEntityCommentEntity>
@Insert
funfun insert(varargvararg comments: CommentEntityCommentEntity)
@Query("SELECT * FROM links")
funfun allLinks(): ListList<LinkEntityLinkEntity>
@Insert
funfun insert(varargvararg links: LinkEntityLinkEntity)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4G*'4/*- ?&/K
*2 1 -<*).0( -.*! PolyStore ($"#/+- ! -/*2*-&2$/# Note *% /.<$")*-$)"
/#  /$'.*!2# /# -/#*. )*/ .- *(( )/.*-'$)&.?*-/#/<2 #1 /*2-$/
*0-*2)*)- / !0)/$*).>
@Transaction
funfun allNotes() = allComments() + allLinks()
@Transaction
funfun insert(varargvararg notes: NoteNote) {
insert(*notes.filterIsInstance(CommentEntityCommentEntity::classclass.java).toTypedArray())
insert(*notes.filterIsInstance(LinkEntityLinkEntity::classclass.java).toTypedArray())
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4G*'4/*- ?&/K
allNotes() .$(+'4*($) ./# - .0'/.*!/# allComments() ) allLinks()
!0)/$*).?# Note 1-$)/*! insert() /0-).-*0))''./# CommentEntity
) LinkEntity 1-$)/.*! insert()<8)$)"/# CommentEntity ) LinkEntity
$)./) .$)*0- vararg *! Note *% /.?# !//#/2 - 0.$)" vararg (& .
POLYMORPHIC ENTITIES
105
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
insert() $/*(+'$/ <2$/#.+- *+ -/*-.J*K) toTypedArray() ''.?!
2 2 - 0.$)" List $)./ *! vararg<2 2*0'#1 .*( 2#/.$(+' -* >
@Transaction
funfun insert(notes: ListList<NoteNote>) {
insert(notes.filterIsInstance(CommentEntityCommentEntity::classclass.java))
insert(notes.filterIsInstance(LinkEntityLinkEntity::classclass.java))
}
1$)"/# Note !0)/$*).*) PolyStore ''*2.0./*2*-&2$/#/# *)- / /4+ .
*-/# Note $)/ -! <.2 . 8/>
packagepackage com.commonsware.room.misc.polycom.commonsware.room.misc.poly
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport com.commonsware.room.misc.MiscDatabasecom.commonsware.room.misc.MiscDatabase
importimport com.natpryce.hamkrest.*com.natpryce.hamkrest.*
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass PolyStoreTestPolyStoreTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.polyStore()
@Test
funfun comments() {
assertThat(underTest.allComments(), isEmpty)
valval firstComment = CommentEntityCommentEntity(1, "This is a comment")
valval secondComment = CommentEntityCommentEntity(2, "This is another comment")
underTest.insert(firstComment, secondComment)
assertThat(
underTest.allComments(),
allOf(
hasSize(equalTo(2)),
hasElement(firstComment),
hasElement(secondComment)
POLYMORPHIC ENTITIES
106
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
)
)
assertThat(
underTest.allNotes(),
allOf(
hasSize(equalTo(2)),
hasElement(firstComment asas NoteNote),
hasElement(secondComment asas NoteNote)
)
)
}
@Test
funfun links() {
assertThat(underTest.allLinks(), isEmpty)
valval firstLink = LinkEntityLinkEntity(1, "CommonsWare", "https://commonsware.com")
valval secondLink = LinkEntityLinkEntity(
2,
"Room Release Notes",
"https://developer.android.com/jetpack/androidx/releases/room"
)
underTest.insert(firstLink, secondLink)
assertThat(
underTest.allLinks(),
allOf(
hasSize(equalTo(2)),
hasElement(firstLink),
hasElement(secondLink)
)
)
assertThat(
underTest.allNotes(),
allOf(
hasSize(equalTo(2)),
hasElement(firstLink asas NoteNote),
hasElement(secondLink asas NoteNote)
)
)
}
@Test
funfun notes() {
assertThat(underTest.allNotes(), isEmpty)
POLYMORPHIC ENTITIES
107
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
valval firstComment = CommentEntityCommentEntity(1, "This is a comment")
valval secondComment = CommentEntityCommentEntity(2, "This is another comment")
valval firstLink = LinkEntityLinkEntity(1, "CommonsWare", "https://commonsware.com")
valval secondLink = LinkEntityLinkEntity(
2,
"Room Release Notes",
"https://developer.android.com/jetpack/androidx/releases/room"
)
underTest.insert(firstComment, secondComment, firstLink, secondLink)
assertThat(
underTest.allNotes(), allOf(
hasSize(equalTo(4)),
hasElement(firstLink asas NoteNote),
hasElement(secondLink asas NoteNote),
hasElement(firstComment asas NoteNote),
hasElement(secondComment asas NoteNote)
)
)
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G+*'4G*'4/*-  ./?&/K
Can I JOINJOIN a UNIONUNION?
*0($"#//#$)&/#/2 *0'- / allNotes() 0.$)"/# UNION .0++*-/$)$/ ?
#$..$''4''*2.4*0/**)/ )/ /2*,0 -$ .)*($) /# $-- .0'/.?
# /# *-42*0' /#/4*0*0'*.*( /#$)"'$& /#$.>
@Query("SELECT * FROM links UNION ALL SELECT * FROM comments")
funfun allNotes(): ListList<NoteNote>
*2 1 -</#$.2$'')*/2*-&?
)/#$..+ $8. < links ) comments *)*/#1 /# .( *'0().<.*0-
)/$/$ .#1 $7 - )/8 '.?#$.-0).!*0'*! UNION - "0'/$*).<./($)$(0(<
*/##'1 .*!/# UNION #1 /*- /0-)/# .( )0( -*!*'0().?
 4*)/#/<**(#.)*24/*&)*22#$#-*2.- '$)&.)2#$#-*2.-
*(( )/.<./# - $.)*/#$)"/*$./$)"0$.#/# ($)/# - .0'/. /?
POLYMORPHIC ENTITIES
108
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
$)''4<**())*/- / $)./) .*! Note<./#/$.)$)/ -! <)2 2)/
LinkEntity ) CommentEntity *% /.)424?#/2*0'- ,0$- **(/*)*/
*)'4&)*22#$#-*2.- '$)&.)2#$#- *(( )/.<0//#/-*2./#/-
'$)&..#*0' /0-) $)/* LinkEntity *% /.)/#/-*2./#/- *(( )/.
.#*0' /0-) $)/* CommentEntity *% /.?
-*(+-/$'./)+*$)/<*/# )/$/$ .2*0') /*#1 /# .( +-*+ -/$ .
)- .0'/$)".# (?# - .0'/. /J (*$ $) CursorK#.*)'4*) . /*!
*'0())( .<. *)/# 8-./,0 -4$)/# UNION?**(2*0') /* ' /*
 / -($) #*2/*+*+0'/  )/$/$ .!-*(/# . *),0 -40.$)"/# 8-./,0 -4D.
*'0())( .?)'''$& '$#**</#/2*0'- ,0$- /# )( ./* /# .( $)
*/#,0 -$ .)$)*/# )/$/$ .?
0 /*/# . '$($//$*).<$/$.0)'$& '4/#/**(2$''" //#$.+$'$/4</#*0"#$/$.
)*/$(+*..$' ?
Polymorphism With a Single Table
*0'"*/# */# --*0/ >#1 .$)"' /' !*-'')*/ *% /.<- "-' ..*!
2# /# -/# 4- *(( )/*-'$)&?*-.(''*% /.2$/#! 2+-*+ -/$ .<2$/#
'*/*!*1 -'+ /2 )/# +-*+ -/$ .*!/# *)- / /4+ .</#$.$.()" ' ?/
 *( .0)2$ '4!*-()4*)- / /4+ .2$/#()4$.+-/ +-*+ -/$ .?/'.*
+0/.'$($/.*)4*0-<./# *)'4+-/$' NOT NULL *'0().- *) .!*-2#$#
4*0).0++'41'0 .!*- 1 -4+*..$' *)- / /4+ ?*0'.*) .*( 24*!
 / -($)$)"2#/*)- / /4+ /*0. !*-)4"$1 )/' -*2<)*!/ )/#/
- ,0$- .4 / another *'0()?
0/<$/ is )*+/$*)?
# polysingle .0A+&" $) /# MiscSamples (*0'  (*)./-/ ./#$.
++-*#?#$./$( </#  )/$/4$. NoteEntity>
packagepackage com.commonsware.room.misc.polysinglecom.commonsware.room.misc.polysingle
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "notes")
openopen classclass NoteEntityNoteEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong,
POLYMORPHIC ENTITIES
109
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
valval title: StringString,
varvar url: StringString?
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4.$)"' G*/ )/$/4?&/K
/*)/$)..0+ -. /*!''*'0().!-*(*/#*0-*(( )/.)'$)&.?)/#$.. <
/#/%0./( )./#//# $.*+/$*)'H'$)&#.*) <0/*(( )/* .)*/?
#$.++-*#2$''" /'*/(*- ( ..4$!4*0#1 '*/.*! )/$/$ .)'*/.*!
*'0()./#/ 3$./*)'4$).0. /*!/#*.  )/$/4/4+ .</#*0"#?
*2< Comment ) Link - .0'.. .*! NoteEntity<$(+' ( )/$)"/# Note
$)/ -! !-*(/# poly +&" )*1 --$$)" displayText .)  !*-/# $-
. )-$*.>
packagepackage com.commonsware.room.misc.polysinglecom.commonsware.room.misc.polysingle
importimport com.commonsware.room.misc.poly.Notecom.commonsware.room.misc.poly.Note
classclass CommentComment(id: LongLong, title: StringString) : NoteEntityNoteEntity(id, title, nullnull), NoteNote {
overrideoverride valval displayText: CharSequenceCharSequence
getget() = title
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4.$)"' G*(( )/?&/K
packagepackage com.commonsware.room.misc.polysinglecom.commonsware.room.misc.polysingle
importimport androidx.core.text.HtmlCompatandroidx.core.text.HtmlCompat
importimport com.commonsware.room.misc.poly.Notecom.commonsware.room.misc.poly.Note
classclass LinkLink(
id: LongLong,
title: StringString,
url: StringString
) : NoteEntityNoteEntity(id, title, url), NoteNote {
overrideoverride valval displayText: CharSequenceCharSequence
getget() = HtmlCompatHtmlCompat.fromHtml(
"""<a href="$url">$title</a>""",
HtmlCompatHtmlCompat.FROM_HTML_MODE_COMPACTFROM_HTML_MODE_COMPACT
)
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4.$)"' G$)&?&/K
*0($"#/-"0 /#/ NoteEntity .#*0' abstract ) 8) displayText /# - ?
#/*0'2*-&<//# *./*!)*/ $)"' /*'* NoteEntity *% /.$- /'4<.
POLYMORPHIC ENTITIES
110
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
**())*/- / $)./) .*!) abstract '..?
PolySingleStore H/# !*-/#$.. )-$*H$.$/.$(+' -? *)*/) 
 $/  insert() !0)/$*).!*- Link ) Comment<.) insert() !0)/$*)!*-
NoteEntity *1 -.*/#*!/#*. . .? allLinks() ) allComments() )/&
1)/" *!**(D.- /0-)/4+ : 3$$'$/4<#1$)"**(- / Link ) Comment
*% /.$- /'4<2$/#*0-,0 -4- /0-)$)"/# +-*+ --*2.. *)2# /# -2 #1
url *-)*/>
packagepackage com.commonsware.room.misc.polysinglecom.commonsware.room.misc.polysingle
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.Insertandroidx.room.Insert
importimport androidx.room.Queryandroidx.room.Query
@Dao
interfaceinterface PolySingleStorePolySingleStore {
@Query("SELECT * FROM notes")
funfun allNotes(): ListList<NoteEntityNoteEntity>
@Insert
funfun insert(varargvararg notes: NoteEntityNoteEntity)
@Query("SELECT * FROM notes WHERE url IS NOT NULL")
funfun allLinks(): ListList<LinkLink>
@Query("SELECT id, title FROM notes WHERE url IS NULL")
funfun allComments(): ListList<CommentComment>
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G+*'4.$)"' G*'4$)"' /*- ?&/K
)<*) "$)<2 #1 .09$ )/*+ -/$*).)*2/* ' /*()$+0'/
'$)&.)*(( )/.. +-/ '4*-/- /$)"/# (''.)*/ .>
packagepackage com.commonsware.room.misc.polysinglecom.commonsware.room.misc.polysingle
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.test.ext.junit.runners.AndroidJUnit4androidx.test.ext.junit.runners.AndroidJUnit4
importimport androidx.test.platform.app.InstrumentationRegistryandroidx.test.platform.app.InstrumentationRegistry
importimport com.commonsware.room.misc.MiscDatabasecom.commonsware.room.misc.MiscDatabase
importimport com.natpryce.hamkrest.anyOfcom.natpryce.hamkrest.anyOf
importimport com.natpryce.hamkrest.assertion.assertThatcom.natpryce.hamkrest.assertion.assertThat
importimport com.natpryce.hamkrest.equalTocom.natpryce.hamkrest.equalTo
importimport com.natpryce.hamkrest.hasSizecom.natpryce.hamkrest.hasSize
importimport com.natpryce.hamkrest.isEmptycom.natpryce.hamkrest.isEmpty
POLYMORPHIC ENTITIES
111
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
importimport org.junit.Testorg.junit.Test
importimport org.junit.runner.RunWithorg.junit.runner.RunWith
@RunWith(AndroidJUnit4AndroidJUnit4::classclass)
classclass PolySingleStoreTestPolySingleStoreTest {
privateprivate valval db = RoomRoom.inMemoryDatabaseBuilder(
InstrumentationRegistryInstrumentationRegistry.getInstrumentation().targetContext,
MiscDatabaseMiscDatabase::classclass.java
)
.build()
privateprivate valval underTest = db.polySingleStore()
@Test
funfun comments() {
assertThat(underTest.allComments(), isEmpty)
valval firstComment = CommentComment(1, "This is a comment")
valval secondComment = CommentComment(2, "This is another comment")
underTest.insert(firstComment, secondComment)
valval allComments = underTest.allComments()
assertThat(allComments, hasSize(equalTo(2)))
assertThat(
allComments[0].title,
anyOf(equalTo(firstComment.title), equalTo(secondComment.title))
)
assertThat(
allComments[1].title,
anyOf(equalTo(firstComment.title), equalTo(secondComment.title))
)
valval allNotes = underTest.allNotes()
assertThat(allNotes, hasSize(equalTo(2)))
assertThat(
allNotes[0].title,
anyOf(equalTo(firstComment.title), equalTo(secondComment.title))
)
assertThat(
allNotes[1].title,
anyOf(equalTo(firstComment.title), equalTo(secondComment.title))
)
}
@Test
funfun links() {
POLYMORPHIC ENTITIES
112
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
assertThat(underTest.allLinks(), isEmpty)
valval firstLink = LinkLink(1, "CommonsWare", "https://commonsware.com")
valval secondLink = LinkLink(
2,
"Room Release Notes",
"https://developer.android.com/jetpack/androidx/releases/room"
)
underTest.insert(firstLink, secondLink)
valval allLinks = underTest.allLinks()
assertThat(allLinks, hasSize(equalTo(2)))
assertThat(
allLinks[0].title,
anyOf(equalTo(firstLink.title), equalTo(secondLink.title))
)
assertThat(
allLinks[1].title,
anyOf(equalTo(firstLink.title), equalTo(secondLink.title))
)
valval allNotes = underTest.allNotes()
assertThat(allNotes, hasSize(equalTo(2)))
assertThat(
allNotes[0].title,
anyOf(equalTo(firstLink.title), equalTo(secondLink.title))
)
assertThat(
allNotes[1].title,
anyOf(equalTo(firstLink.title), equalTo(secondLink.title))
)
}
@Test
funfun notes() {
assertThat(underTest.allNotes(), isEmpty)
valval firstComment = CommentComment(1, "This is a comment")
valval secondComment = CommentComment(2, "This is another comment")
valval firstLink = LinkLink(3, "CommonsWare", "https://commonsware.com")
valval secondLink = LinkLink(
4,
"Room Release Notes",
"https://developer.android.com/jetpack/androidx/releases/room"
)
POLYMORPHIC ENTITIES
113
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
underTest.insert(firstComment, secondComment, firstLink, secondLink)
valval allNotes = underTest.allNotes()
assertThat(allNotes, hasSize(equalTo(4)))
assertThat(
allNotes[0].title,
anyOf(
equalTo(firstComment.title),
equalTo(secondComment.title),
equalTo(firstLink.title),
equalTo(secondLink.title)
)
)
assertThat(
allNotes[1].title,
anyOf(
equalTo(firstComment.title),
equalTo(secondComment.title),
equalTo(firstLink.title),
equalTo(secondLink.title)
)
)
assertThat(
allNotes[2].title,
anyOf(
equalTo(firstComment.title),
equalTo(secondComment.title),
equalTo(firstLink.title),
equalTo(secondLink.title)
)
)
assertThat(
allNotes[3].title,
anyOf(
equalTo(firstComment.title),
equalTo(secondComment.title),
equalTo(firstLink.title),
equalTo(secondLink.title)
)
)
}
}
J!-*( $.(+' .G.-G)-*$ ./G%1G*(G*((*).2- G-**(G($.G+*'4.$)"' G*'4$)"' /*- ./?&/K
POLYMORPHIC ENTITIES
114
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Default Values and Partial Entities
#1 '- 4. )#*2**(.0++*-/.*/'$) !0'/1'0 .*)+-*+ -/$ .>
classclass CustomColumnNameEntityCustomColumnNameEntity(
@PrimaryKey
valval id: StringString,
valval title: StringString,
@ColumnInfo(name = "words") valval text: StringString? = nullnull,
valval version: IntInt = 1
) {
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0./*(*'0()( )/$/4?&/K
 - < text ) version */##1  !0'/1'0 .? ''4</# . #1 )*/#$)"/**
2$/#**(H/# 4- +0- */'$)*) +/?*2 1 -<$)/#  )<$!2 - / 
CustomColumnNameEntity $)./) 2$/#*0/.0++'4$)" text *- version<2 " //#*.
 !0'/.?
0/<$)**(Q?Q?O<2 "*/)*/# -*+/$*)!*- !0'/1'0 .@*) /#/*)/#
.0-! . (.+*$)/' ..<.$/* .)*/2*-&2$/# @Insert *+ -/$*).?-$)$+''4<
/#$.++ -./* /$ /*)*/# -! /0-  $)/#/.( - ' . >+-/$' )/$/4
.0++*-/?
)/#$.#+/ -<2 2$'' 3+'*- */#*!/# . ! /0- .?
Default Values, and the Other Default Values
# ) 2*+/$*)!*- !0'/1'0 .*( .$)/# !*-(*! defaultValue +-*+ -/4*)
/# @ColumnInfo ))*//$*)>
115
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@Entity(tableName = "defaultValue")
classclass DefaultValueEntityDefaultValueEntity(
@PrimaryKey
valval id: StringString,
valval title: StringString,
@ColumnInfo(defaultValue = "something")
valval text: StringString? = nullnull,
@ColumnInfo(defaultValue = "123")
valval version: IntInt = 1
) {
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G !0'/'0 )/$/4?&/K
 - <.$)/#  3(+' .#*2) -'$ -<2 #1 text ) version +-*+ -/$ .*))
)/$/4JDefaultValueEntityK<)$)*/'$)<2 #1 /#*. +-*+ -/$ . !0'//* null
) 1<- .+ /$1 '4?*2 1 -<2 '.*#1 @ColumnInfo ))*//$*).*)/#*. <2# -
##. defaultValue +-*+ -/4?# *) *) text .4./#  !0'/1'0 $.
something<2#$' /# *) *) version .4./#//#  !0'/1'0 $. 123?*<!*- #
+-*+ -/4<2 #1 /2*. +-/  !0'/1'0 . '- >*) 1$*/'$))*) 1$
/# defaultValue ))*//$*)+-*+ -/4@)*/#- $7 - )/?
# */'$) !0'/1'0 .- !*-*/'$)D.0. ?*0)- / $)./) .*!
DefaultValueEntity 2$/#*0/.0++'4$)"1'0 .!*- text )G*- version?
# @ColumnInfo defaultValue +-*+ -/$ .- !*-$/ D.0. ?!4*0// (+//*
INSERT 1'0 $)/*/# /' 2$/#*0/+-*1$$)"1'0 .!*-/# text )G*- version
*'0().</# defaultValue +-*+ -/$ ./&  7 /?.$''4</# defaultValue
+-*+ -/$ .-  (  $- /'4$)/# CREATE TABLE .// ( )/" ) -/
!-*(*0- @Entity  '-/$*)>
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS `defaultValue` (`id` TEXT NOTNOT NULLNULL, `title` TEXT NOTNOT NULLNULL,
`text` TEXT DEFAULTDEFAULT 'something', `version` INTEGER NOTNOT NULLNULL DEFAULTDEFAULT 123,
PRIMARYPRIMARY KEYKEY(`id`))
Default Values and Inserts
!2 @Insert *0- )/$/4</#*0"#</# 1'0 .!*-''*'0()." /.+ $8 1$
+-*+ -/$ .*)/#/ )/$/4?!/ -''<- "-' ..*!2# /# -*-)*/2 +-*1$ text *-
DEFAULT VALUES AND PARTIAL ENTITIES
116
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
version 2# )2 ''/# DefaultValueEntity *)./-0/*-</# - .0'/$)"*% /2$''
#1 1'0 .!*-/#*. +-*+ -/$ .<%0./1$/# */'$) !0'/.?*<*0-- .0'/$)" INSERT
.// ( )/2$''+-*1$ 1'0 .!*-''*!/#*. +-*+ -/$ .<( )$)"/#//# 
 !0'/1'0 .2$'')*/ 0. ?
*<*) "$)>
*2 1 -< @Insert $.)*//# *)'4*+/$*)!*- 3 0/$)" INSERT .// ( )/.$)**(?
)'.**/#/0.$)" @Query>
@Insert
funfun insert(entity: DefaultValueEntityDefaultValueEntity)
@Query("INSERT INTO defaultValue (id, title) VALUES (:id, :title)")
funfun insertByQuery(id: StringString, title: StringString)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G !0'/'0 )/$/4?&/K
 - 2 #1 /2*!0)/$*).!*-$). -/$)"$)/**0- defaultValue /' ?# 8-./
0. . @Insert?# . *)0. . @Query<2$/# INSERT .// ( )//#/* .)*/
+-*1$ 1'0 .!*- text *- version?*<2# )2 '' insertByQuery()<*0-- .0'/$)"
/. /' -*22$''*)/$)/# .0++'$  id ) title 1'0 .<+'0./# SQL
default values !*- text ) version?
Partial Entities
-$*-/*/# $)/-*0/$*)*!.0++*-/!*- !0'/1'0 .<2 '24.#/*+-*1$
1'0 .!*-''*'0().$)*0- INSERT .// ( )/.?!4*0$). -/ .*' '41$ @Insert<
4*0($"#/)*/)*/$ /#$.'$($//$*)?# !//#/2 )*2#1  !0'/1'0
.0++*-/( )./#//# E$). -/A4A@QueryF*+/$*)#."- / -: 3$$'$/4>2 *)*/
#1 /*+-*1$ ''1'0 .!*-''*'0().?
 '/ /*/#/$.+-/$' )/$/4.0++*-/<2# - 2 )#1 *-$)-4 @Insert 
!0)/$*).'.**)'4.0++'4.0. /*!+-*+ -/$ ./*"*$)/*/# /*A A$). -/ -*2?
*1 -  -'$ -$)/# **&#*22 )0. -$/--4.*-*/'$)/
'.. .!*-/# *0/+0/*! @Query !0)/$*).>
DEFAULT VALUES AND PARTIAL ENTITIES
117
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
importimport kotlin.random.Randomkotlin.random.Random
data classdata class CountAndSumResultCountAndSumResult(valval count: IntInt, valval sum: LongLong)
@Entity(tableName = "aggregate")
classclass AggregateEntityAggregateEntity(
@PrimaryKey(autoGenerate = truetrue)
valval id: LongLong = 0,
valval value: LongLong = RandomRandom.nextLong(1000000)
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM aggregate")
funfun loadAll(): ListList<AggregateEntityAggregateEntity>
@Query("SELECT COUNT(*) FROM aggregate")
funfun count(): IntInt
@Query("SELECT COUNT(*) as count, SUM(value) as sum FROM aggregate")
funfun countAndSum(): CountAndSumResultCountAndSumResult
@Insert
funfun insert(entities: ListList<AggregateEntityAggregateEntity>)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G""- "/ )/$/4?&/K
 - <2 0. CountAndSumResult /*(* '/# - .+*). !-*(>
SELECTSELECT COUNTCOUNT(*) asas countcount, SUMSUM(value) asas sumsum FROMFROM aggregateaggregate
$($'-'4<2 )0. *-*/'$)/'../*(* '+-/$'$)+0//*)
@Insert< @Update<*- @Delete !0)/$*)>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
data classdata class PartialDefaultValuePartialDefaultValue(
valval id: StringString,
valval title: StringString
)
DEFAULT VALUES AND PARTIAL ENTITIES
118
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@Entity(tableName = "defaultValue")
classclass DefaultValueEntityDefaultValueEntity(
@PrimaryKey
valval id: StringString,
valval title: StringString,
@ColumnInfo(defaultValue = "something")
valval text: StringString? = nullnull,
@ColumnInfo(defaultValue = "123")
valval version: IntInt = 1
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM defaultValue")
funfun loadAll(): ListList<DefaultValueEntityDefaultValueEntity>
@Query("SELECT * FROM defaultValue where id = :id")
funfun findByPrimaryKey(id: StringString): DefaultValueEntityDefaultValueEntity
@Insert
funfun insert(entity: DefaultValueEntityDefaultValueEntity)
@Query("INSERT INTO defaultValue (id, title) VALUES (:id, :title)")
funfun insertByQuery(id: StringString, title: StringString)
@Insert(entity = DefaultValueEntityDefaultValueEntity::classclass)
funfun insertPartial(partial: PartialDefaultValuePartialDefaultValue)
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G !0'/'0 )/$/4?&/K
)$/$*)/*/# insert() ) insertByQuery() !0)/$*).<2 #1
insertPartial()?#$. +/. PartialDefaultValue *% /)0. ./#/!*-)
INSERT $)/**0-/' ?
4 !0'/</#$.2*0'!$'/*(+$' /$( <2$/#/# **())*//$*)+-* ..*-
*(+'$)$)"/#/ PartialDefaultValue $.)*/) )/$/4?*2 1 -<*)/# @Insert
))*//$*)!*- insertPartial()<2 0. ) entity +-*+ -/4/*/ #**(2#/
@Entity $...*$/ 2$/#/#$. @Insert *+ -/$*)>
@Insert(entity = DefaultValueEntityDefaultValueEntity::classclass)
funfun insertPartial(partial: PartialDefaultValuePartialDefaultValue)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G !0'/'0 )/$/4?&/K
# *-*/'$)/'../#/4*0.0++'4.$)+0/JPartialDataEntityK2$''
DEFAULT VALUES AND PARTIAL ENTITIES
119
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
0. '$& /#  )/$/4$/. '!<. *)$)$1$0'+-*+ -/4)( .?**(2$'''**&$)/#
.0++'$ *% /!*-+-*+ -/$ .(/#$)"/#*. *!/#  )/$/4'..)0. /# *) .
/#/$/8).?*- 1 -4/#$)" '. H.0#. text ) version $)/#$.. H**(
2$''- '4*)/# 0) -'4$)"/'  $/# -.0++*-/$)" NULL !*-/# *'0()@*-#1$)"
defaultValue .0++'$ ?
)4 1 '*+ -.2$'')*/) /# . ! /0- .?*/'$)+-*+ -/4 !0'/1'0 .))
*-$)-4 @Insert 2$''.09 ?0/</# . ! /0- .(& $/$/ .$ -/*(* ''..$
/. ./-0/0- .)(4# '+$).*( . )-$*.?
DEFAULT VALUES AND PARTIAL ENTITIES
120
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room and Full-Text Search
$/ .0++*-/. 1$-/0'/' . !*-!0''A/ 3/. -#$)"*!*)/ )/?./# $/
*0( )//$*)+0/.$/>
# (*./*((*)J) 7 /$1 K24/* .-$ !0''A/ 3/. -# .$.E2#/
**"' <#**<)$)"*2$/#*0( )/.+' *)/# *-'$
F?
-$"$)''4<**($)*/#1 )4.0++*-/!*-<0/$)QOPX**"'  
.0++*-//***(Q?Q?O?*<$)0-- )/1 -.$*).*!**(<4*0)0. $)4*0-
)-*$++<!*--$#,0 -$ .*!'*)"+$  .*!/ 3/?
)/#$.#+/ -<2 2$'' 3+'*- (*- *0//#$.+$'$/4?
What Is FTS?
/)-/. .- "- /!*-*-$)-4,0 -$ .?)+-/$0'-<2# )$/*( .
/*/ 3/</. .- "- /!*-8)$)"-*2.2# -  -/$)*'0()1'0
(/# .+-/$0'-./-$)"?# 4- 0.0''4+- //4"***0/8)$)"2# )
*'0()1'0 (/# .+-/$0'-./-$)"+- 83<$!/# - $.)$) 3*)/#/*'0()?
#$)"../-//*- &*2)2# )4*02)//*. -#!*-)*0-- ) *!./-$)"$)
*'0()HE8)''-*2.2# - /# *'0() prose *)/$)./# 2*- vagueFH.
/#$.0.0''4- ,0$- .E/' .)FJ$? ?<$/ -/$1 '4 3($)$)" #-*2/*. $!/#$.
(/# .K?)" //$)"(*- *(+' 3/#)/#/$.*!/ )$(+*..$' <*-/' ./-/# -
$90'/?
$/ <$)$/../*&!*-(<$)# -$/.''/#*. +$'$/$ .)'$($//$*).?*2 1 -<
$/ '.**7 -.!0''A/ 3/$) 3$)"<2# - 2 ). -#*0-/. (0#'$&
#*22 0. . -# )"$) J ?"?<E8)''-*2.2# - /#$.*'0()#.*/# foo )
121
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
bar $)$/.*( 2# - FK?#$' !0''A/ 3/$) 3/& .0+$/$*)'$.&.+ </#
.+ *!/# !0''A/ 3/. -#$)"$.,0$/ $(+- ..$1 ?
A Word About SQLite Versions
$/ #. 1*'1 .$) )-*$D.$)$/$'+-*0/$*)- ' . $)QOOW?
)()4. .<)-*$* .)*/$)*-+*-/ 0+/ ./*/#$-A+-/4* <!*-
&2-.A*(+/$$'$/4- .*).J ?"?<+# D.//+'$ )/K?)/# . *!$/ <
) 2 -)-*$1 -.$*).*/& *)) 2 -1 -.$*).*!$/ @0//#  3/1 -.$*)
*!$/ /#/"$1 )1 -.$*)*!)-*$0. .$.0)*0( )/ ?*-. <.*(
 1$ ()0!/0- -.- +' /# ./*&$/ !*-1 -.$*)*!)-*$2$/#
$7 - )/*) ?
#$./&1 -:*2).2 - *)/$).(++$)"*!)-*$- ' . ./*$/
1 -.$*).<$)'0$)"1-$*0.E)*('$ .F2# - ()0!/0- -.#1  ' / /*.#$+
.*( /#$)" '. ?
)()4. .</# $/ 1 -.$*)* .)*/(// -?*- $/ +$'$/$ .2$''#1
3$./ .$) /#  -'$ ./4.*!)-*$?*2 1 -<!0''A/ 3/$) 3$)"$)*/ 3$./$)
/# 8-./$/ 0. 4)-*$<( )$)"/#/4*02$''#1 /*+4// )/$*)/*
4*0- minSdkVersion )$(#$"# )*0"#/#/ 1$ ..#*0'.0++*-//# !0''A/ 3/
$) 3$)"*+/$*)4*0#**. ?
*/ /#/4*0*0'0. ) 3/ -)'$/ $(+' ( )//$*)<*) /#/"$1 .4*0
) 2 -$/  )"$) /#)2#/($"#/ *)/#  1$ ?*- 3(+' < $+# -!*-
)-*$ .#$+.$/.*2)*+4*!$/ J2$/#/# $+# - 3/ ).$*).*(+$' $)K<
*) /#/$.*!/ )) 2 -/#)/# *) /#/$.& $)/*/# 8-(2- *!)4"$1 )
 1$ ?
FTS3, FTS4, and FTS5
# - - /#- !0''A/ 3/$) 3$)"*+/$*).1$'' $)$/ >R<S<) T?
**"' <2$/#**(<*7 -..0++*-/!*-R)S<)*/T?- .0('4<**"'
#. / -($) /#/! 2 1$ ()0!/0- -. )' T?!4*0 +&" 4*0-*2)
$/ 1 -.$*)<4*0*0' ).0- /#/T$.1$'' /*4*0? -#+.$)/# !0/0- <
**"' 2$''T.0++*-/?
*(+-$)"R)S<S) (0#!./ -*) -/$),0 -$ .</#*0"#
*1 -''/# .+ *!/# /2*$(+' ( )//$*)..#*0' .$($'-?S(4/& $/
(*- $.&.+ !*-$/.$) 3 .</#*0"#?
ROOM AND FULL-TEXT SEARCH
122
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Applying FTS to Room
# FTS (*0' *! /# **&D.+-$(-4.(+' +-*% /  (*)./-/ ./# 0. *!S
$)**(?
# ++$./-$1$' A**&-  -?) assets/<$/*)/$).*+4*! /# -*% /
0/ ) -" $/$*)*!?? ''.DE# )1$.$' )F?#$.$..1 $)+'$)/ 3/
8' .<2$/#')&'$) .. -1$)".+-"-+#. +-/*-.?# -  -$/. '!.$(+'4
$.+'4./# **&$)$/. )/$- /4<*) +-"-+#+ --*2$) RecyclerView>
Figure 2: FTS Sample App, As Initially Launched
/'.*#. SearchView<)$!4*0. -#*)/ -(<4*02$'' "$1 ).)$++ /.*!
/# **&.#*2$)"2# - /#/2*-2.0. ?
#$.#+/ -2$''!*0.*)/# BookRepository )$/...*$/ **('.. ./#/
#)' ./*-$)"/# **&*)/ )/)*)0/$)"/# . -# .?
Creating the FTS Table and Entity
#$' /# **&$.+&" 2$/#/# ++$) assets/ .+'$)/ 3/8' .<$/ )
ROOM AND FULL-TEXT SEARCH
123
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#1 )*24*!. -#$)"/#/*)/ )/?*</# ++#.$/ /' )
..*$/ **( )/$/4!*-/# / 3/?$) /# +-*. $.$1$ $)/*+-"-+#.<2
#1  ParagraphEntity /#/(* './#*. >
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
@Entity(tableName = "paragraphs")
data classdata class ParagraphEntityParagraphEntity(
@PrimaryKey(autoGenerate = truetrue) valval id: LongLong,
valval sequence: IntInt,
valval prose: StringString
)
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G-"-+#)/$/4?&/K
 - < sequence $.)$)/ " -.#*2$)"2# - /# +-"-+#++ -.$)/# **&<2$/#
'*2 -)0( -.( )$)" -'$ -++ -) .? prose $./# / 3/*!/# +-"-+#$/. '!?
# - - ! 2$/ ./-/ "$ .!*-- /$)")($)/$)$)")$) 3?#$.
**&"* .2$/#+-'' 'A/' ++-*#<2# - 2 )*/*)'4#1 ) )/$/4)/'
!*-/# /<0/. +-/  )/$/4)/' !*-/# $) 3?
*/#/ )<2 )*/*)'4#1 ParagraphEntity<0/2 #1 ParagraphFtsEntity>
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.Fts4androidx.room.Fts4
@Fts4(contentEntity = ParagraphEntityParagraphEntity::classclass)
@Entity(tableName = "paragraphsFts")
data classdata class ParagraphFtsEntityParagraphFtsEntity(valval prose: StringString)
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G-"-+#/.)/$/4?&/K
#  )/$/4#./# .( +-*+ -/$ ../# ($) )/$/4<!*-/#*. *'0()./#/2
2)//* $) 3 4?)/#$.. </#/$.*)'4/# prose?
# @Fts4 ))*//$*)$)$/ ./#//#$.$.) )/$/4- +- . )/$)")SE.#*2
/' F!*-.*( */# -/' ?# contentEntity +-*+ -/4*!/# ))*//$*)+*$)/.
**(/*/# ($) )/$/4JParagraphEntity $)/#$.. K?
ROOM AND FULL-TEXT SEARCH
124
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
) -/# *1 -.<**()$/ D.S2$''>
I ./)- rowid *'0()/*/# .#*2/' <(/#$)"/# INTEGER 
*!/# ($)/'
I /-$"" -.<.0#/#/2# ) 1 -#)" .- ( /*/# ($)/' <**(
)0+/ /# .#*2/' /*(/#
.- .0'/<2#$' 2 2$'',0 -40.$)"*/#/' .<2 2$''*)'4$). -/*)/ )/0.$)"
ParagraphEntity H ParagraphFtsEntity 2$'' #)' !*-0.?
Querying Using FTS
0-$. BookStore>
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.Insertandroidx.room.Insert
importimport androidx.room.Queryandroidx.room.Query
@Dao
abstractabstract classclass BookStoreBookStore {
@Insert
abstractabstract suspendsuspend funfun insert(paragraphs: ListList<ParagraphEntityParagraphEntity>)
@Query("SELECT prose FROM paragraphs ORDER BY sequence")
abstractabstract suspendsuspend funfun all(): ListList<StringString>
@Query("SELECT snippet(paragraphsFts) FROM paragraphs JOIN paragraphsFts "+
"ON paragraphs.id == paragraphsFts.rowid WHERE paragraphsFts.prose "+
"MATCH :search ORDER BY sequence")
abstractabstract suspendsuspend funfun filtered(search: StringString): ListList<StringString>
}
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&/*- ?&/K
.)*/ *1 <*0-*)'4/A()$+0'/$*)!0)/$*)H insert() H2*-&.2$/#
ParagraphEntity -/# -/#) ParagraphFtsEntity?)<2 - 2 '*( /*,0 -4
ParagraphEntity .)*-('<2$/#*0/#1$)"/* '2$/#<.2 *$)/# all()
!0)/$*)/*- /-$ 1 ''/# +-*. $). ,0 )/$'*- -?
*2 1 -<2 )'.*0. /# MATCH *+ -/*-!*-,0 -$ ."$)./*0-S.#*2
/' ? MATCH /& . . -# 3+- ..$*)<2#$#*0' ..$(+' .2*-/*8)$)
/# $) 3 *)/ )/?
ROOM AND FULL-TEXT SEARCH
125
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*-/# (*( )/<' /D.+- / )/#//# filtered() !0)/$*)*) BookStore '**& 
'$& /#$.>
@Query("SELECT prose FROM paragraphs JOIN paragraphsFts "+
"ON paragraphs.id == paragraphsFts.rowid WHERE paragraphsFts.prose "+
"MATCH :search ORDER BY sequence")
abstractabstract suspendsuspend funfun filtered(search: StringString): ListList<StringString>
# SELECT prose FROM paragraphs )/# ORDER BY sequence +-/.- .$(+'
?*($) </# 4(& 0+/# ,0 -40. $) all()</*- /0-)''/# +-*. <*- -
4 sequence?
*2 1 -< filtered() '.*0. . JOIN /**)) //# paragraphsFts /' 2$/#/#
paragraphs /' ?# id *!*0- ParagraphEntity 2$''(/#/# rowid *!/#
*-- .+*)$)" ParagraphsFtsEntity $)$/..#*2/' ?
$)''4<2 0. WHERE paragraphsFts.prose MATCH :search /*++'4)S. -#
"$).//# SA$) 3 +-*. $) paragraphsFts?#-*0"#/# JOIN<2 )" /
*'0().&!-*( paragraphs !*-2#/ 1 --*2.$) paragraphsFts (/# /#
S. -# 3+- ..$*)?
.- .0'/<''$)"/#$.1 -.$*)*! filtered() 2$/#)S. -# 3+- ..$*)2*0'
"$1 0.''/# *(+' / / 3/*!''/# +-"-+#./#/(/# /#/. -#
3+- ..$*)?
Getting Snippets
*2 1 -@/# filtered() !0)/$*).#*2)*1 $.)*//0''42#/ BookStore #.?
)./ <$/#.>
@Query("SELECT snippet(paragraphsFts) FROM paragraphs JOIN paragraphsFts "+
"ON paragraphs.id == paragraphsFts.rowid WHERE paragraphsFts.prose "+
"MATCH :search ORDER BY sequence")
abstractabstract suspendsuspend funfun filtered(search: StringString): ListList<StringString>
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&/*- ?&/K
 - <$)./ *!- /-$ 1$)" prose !-*( paragraphs<2 #1 snippet(paragraphFts)?
ROOM AND FULL-TEXT SEARCH
126
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# )4*00. )/ -) /. -# )"$) .'$& **"' <0&0&*< /?<0.0''4/# $-
. -#- .0'/.- )*/%0./'$)&.<0/.)$++ /.*!/ 3/.#*2$)"4*0/# *)/ 3/-*0)
4*0-. -#/ -(<*!/ )2$/#. -#& 42*-#$"#'$"#/ >
Figure 3: DuckDuckGo Search for CommonsWare, with Keyword Highlights
snippet() "$1 .4*0/# .(  7 /?$/ D. )"$) 2$''- /0-)/*4*0+*-/$*)
*!/# / 3/<2$/#. -#& 42*-.$)*'! ?# 1'0 +.. /*/# snippet()
!0)/$*)$./# )( *!/# /' J$)/#$.. < paragraphFtsK?
# RecyclerView.ViewHolder - ) -./#/0.$)" HtmlCompat>
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport android.view.Viewandroid.view.View
importimport android.widget.TextViewandroid.widget.TextView
importimport androidx.core.text.HtmlCompatandroidx.core.text.HtmlCompat
importimport androidx.recyclerview.widget.RecyclerViewandroidx.recyclerview.widget.RecyclerView
classclass RowHolderRowHolder(root: ViewView) : RecyclerViewRecyclerView.ViewHolderViewHolder(root) {
privateprivate valval prose = root.findViewById<TextViewTextView>(RR.id.prose)
funfun bind(paragraph: StringString) {
prose.text = HtmlCompatHtmlCompat.fromHtml(paragraph, HtmlCompatHtmlCompat.FROM_HTML_MODE_COMPACTFROM_HTML_MODE_COMPACT)
}
}
ROOM AND FULL-TEXT SEARCH
127
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G*2*' -?&/K
#  7 /$./#//# 0. -D.#*. ). -#/ -(.#*2.0+$)*'$)/# . -#
- .0'/.>
Figure 4: FTS Sample App, Showing Search Results for ‘chair’
*0*)*/#1 /*0. /#$.! /0- <$!4*0*)*/2)/<0/$/$.1$'' /*4*0$!$/
2*0' *!0. /*4*0-0. -.?
Populating the Database
0- BookDatabase * .)*/) )4/#$)".+ $'!*-#1$)" +-/*!$/.
)/$/4)($3?*2 1 -< ParagraphFtsEntity $.) )/$/4<.*$/) ./*
'$./ $)/# @Database ))*//$*)>
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
ROOM AND FULL-TEXT SEARCH
128
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
privateprivate constconst valval DB_NAME = "book.db"
@Database(
entities = [ParagraphEntityParagraphEntity::classclass, ParagraphFtsEntityParagraphFtsEntity::classclass],
version = 1
)
abstractabstract classclass BookDatabaseBookDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun bookStore(): BookStoreBookStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, BookDatabaseBookDatabase::classclass.java, DB_NAMEDB_NAME).build()
}
}
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&/. ?&/K
#)$''4< BookRepository ) ./*&)*2 1 )' ..*0/?*2 1 -< somebody
#./*#1 /# %**!$)"/# **&/*/# /. *)8-./-0)<)$)/#$.. <
/#/%*!''.*) BookRepository?/. all() !0)/$*)2$'' '' 2# )/# ++
'0)# .</*+*+0'/ /# RecyclerView /*.#*2/# **&*)/ )/.?*<2 ). $!
all() *) BookStore - /0-).) (+/4'$./H$!$/* .<2 ).0-($. /#//#
/. $. (+/4)2 ) /*8''$/>
suspendsuspend funfun all(): ListList<StringString> {
valval result = db.bookStore().all()
returnreturn ifif (result.isEmpty()) {
importimport()
db.bookStore().all()
} elseelse {
result
}
}
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G**& +*.$/*-4?&/K
# import() !0)/$*)$/ -/ .*1 -''*!/# **&#+/ -.J2#$#- )0( - .*
/# $-*- -) ($)/$) K)" ) -/ . ParagraphEntity *% /.!*- #*!
/# $-+-"-+#.?# - .0'/$)" List *! )/$/$ ./# )" /.$). -/ $)/*/# /.
1$ insert() *) BookStore>
privateprivate suspendsuspend funfun import() = withContext(DispatchersDispatchers.IOIO) {
valval assets = context.assets
valval entities = assets.list(BOOK_DIRBOOK_DIR).orEmpty()
.sorted()
ROOM AND FULL-TEXT SEARCH
129
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
.map { paragraphs(assets.openopen("$BOOK_DIR/$it")) }
.flatten()
.mapIndexed { index, prose -> ParagraphEntityParagraphEntity(0, index, prose) }
db.bookStore().insert(entities)
}
}
// inspired by https://stackoverflow.com/a/10065920/115145
privateprivate funfun paragraphs(stream: InputStreamInputStream) =
paragraphs(stream.reader().readLines())
privateprivate funfun paragraphs(lines: ListList<StringString>) =
lines.fold(listOf<StringString>()) { roster, line ->
whenwhen {
line.isEmpty() -> roster + ""
roster.isEmpty() -> listOf(line)
elseelse -> roster.take(roster.size - 1) + (roster.last() + " ${line.trim()}")
}
}
.map { it.trim() }
.filter { it.isNotEmpty() }
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G**& +*.$/*-4?&/K
 - < BOOK_DIR $ )/$8 ./# $- /*-42$/#$) assets/ 2# - /# #+/ -.- .$ >
privateprivate constconst valval BOOK_DIR = "TheTimeMachine"
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G**& +*.$/*-4?&/K
Dealing With Errors
/$.+*..$' /#//# 0. -2$''/4+ $))$)1'$. -# 3+- ..$*)?$/ #.
.+ $8. -#')"0" <)/# 0. -D.. -# 3+- ..$*)($"#/)*/# - /*/#/?
)/#$.. <*0- filtered() !0)/$*)< $)" suspend !0)/$*)<2$''/#-*2/#
3 +/$*)? )/#/#/)#)' $/#*22 . 8/?
# ++#. SearchViewModel /#/''. filtered() *) BookRepository )
.0-! ./# - .0'/1$ LiveData>
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport android.content.Contextandroid.content.Context
importimport android.util.Logandroid.util.Log
importimport androidx.lifecycle.LiveDataandroidx.lifecycle.LiveData
importimport androidx.lifecycle.ViewModelandroidx.lifecycle.ViewModel
importimport androidx.lifecycle.liveDataandroidx.lifecycle.liveData
ROOM AND FULL-TEXT SEARCH
130
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
classclass SearchViewModelSearchViewModel(
search: StringString,
repo: BookRepositoryBookRepository,
privateprivate valval context: ContextContext
) : ViewModelViewModel() {
valval paragraphs: LiveDataLiveData<ListList<StringString>> = liveData {
trytry {
emit(repo.filtered(search))
} catchcatch (t: ThrowableThrowable) {
LogLog.e("FTS", "Exception with search expression", t)
emit(listOf(context.getString(RR.string.search_error, search)))
}
}
}
J!-*( G.-G($)G%1G*(G*((*).2- G-**(G!/.G -#$ 2* '?&/K
2-+*0- filtered() ''$) tryGcatch?!2 " /) 3 +/$*)<2 '*"$/.
 /$'./**"/<)2 emit() - .0'/2$/#) --*-( .." !*-/# 0. -?)/#$.
. <2 - /&$)"1)/" *!/# !//#/*0-$.'- 4$.+'4$)"/ 3//*/#
0. -H2 %0./- +' /# )*-('. -#- .0'/.2$/#) --*-( .." $)/#$.. ?
'/ -)/$1 '4<2 *0'#1 paragraphs   LiveData *!.*( sealed class 2$/#
Content ) Error $(+' ( )//$*).? Content *0'#*'/# +-"-+#.<2#$'
Error *0'#*'/#  --*-( .." ?#$.2*0'"$1 /# * /#/$.+'4./#
- .0'/.JSearchFragmentK(*- : 3$$'$/4$)$.+'4$)"/#  --*-<' $/2$/#
.*( 2#/"- / -* *(+' 3$/4?
Why Are We Bothering?
!*0-. <4*0($"#/2*) -2#/''/# !0..$.*0/?)/#$..(+' <2 -
'*$)" the entire book $)/*( (*-4<1$/# all() !0)/$*).*)*0- BookStore )
BookRepository?!2 2)//*. -#<2 *0'*.*+0- '4$)( (*-4<2$/#*0/
)4./07?
# - - /#- - .*).2#42 - $(+' ( )/$)"# - >
P? #$' 2 *0'*.$(+' & 42*-. -# .$'4 )*0"#*0-. '1 .<
.0++*-/. -$# -. -# 3+- ..$*). /#)/#/<)2-$/$)"2#*' . -#
3+- ..$*)+-. -) 3 0/*-$."*$)"/* '*/*!2*-&
Q? 2*0')*/#1 /*'*/#  )/$- **&$)/*( (*-4<$!2 $)*/2)/
/*H2 2$'' 3($) /#/(*- $)'/ -#+/ -
R? /2*0' .$''4/*#1 #+/ -*))/# ))*/.#*2$/
ROOM AND FULL-TEXT SEARCH
131
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Supported MATCHMATCH Syntax
# MATCH *+ -/*-.0++*-/.-)" *!,0 -4./-0/0- .<$)'0$)">
I  42*-(/# .J ?"?< AndroidK
I - 83(/# .J ?"?< SQL*K
I #-. (/# .J ?"?< "open source"K
I NEAR< AND< OR<) NOT *+ -/*-.J ?"?< sqlite AND databaseK
# $/ RGS*0( )//$*) #.(*- *)$/..0++*-/ ,0 -4.4)/3?
Migrating to FTS
!4*0#1 '- 4.#$++ 4*0-++<)4*02)//*)$) 3/*)
3$./$)"/' *-) 2/' 2$/#<4*02$'') /*$(+' ( )/($"-/$*)<
.2 $.0.. $) +- 1$*0.#+/ -?
)/#$.. <4*0-($"-/$*)2$'' $/(*- *(+'$/ ?*/*)'42$''4*0) /*
- / 4*0-/' <0/4*02$'''.*) /* 8) .*( /-$"" -./*& +4*0-
($)/' )/# .#*2/' $).4)?
!4*0. /0+-' /* 3+*-/4*0-.# (.<4*02$''#1  ../*/# - ,0$-
?-/'4</#/2$'' $)/# createSql +-*+ -/4!*-4*0- )/$/4>
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`prose`
TEXT NOT NULL, content=`paragraphs`)"
)<+-/'4</#/2$'' $)/# contentSyncTriggers +-*+ -/4<2#$##*'.
'$./*!.// ( )/./*  3 0/ >
"contentSyncTriggers": [
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_paragraphsFts_BEFORE_UPDATE
BEFORE UPDATE ON `paragraphs` BEGIN DELETE FROM `paragraphsFts` WHERE
`docid`=OLD.`rowid`; END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_paragraphsFts_BEFORE_DELETE
BEFORE DELETE ON `paragraphs` BEGIN DELETE FROM `paragraphsFts` WHERE
`docid`=OLD.`rowid`; END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_paragraphsFts_AFTER_UPDATE
AFTER UPDATE ON `paragraphs` BEGIN INSERT INTO `paragraphsFts`(`docid`, `prose`)
VALUES (NEW.`rowid`, NEW.`prose`); END",
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_paragraphsFts_AFTER_INSERT
ROOM AND FULL-TEXT SEARCH
132
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
AFTER INSERT ON `paragraphs` BEGIN INSERT INTO `paragraphsFts`(`docid`, `prose`)
VALUES (NEW.`rowid`, NEW.`prose`); END"
]
*0-($"-/$*)2$'') /*$)'0 ''*!/# . <.*4*0-($"-/ /. .# (
(/# ./# /. .# (/#/) 20. -.2$''0. ?
ROOM AND FULL-TEXT SEARCH
133
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room and Conflict Resolution
*- @Insert ) @Update ( /#*.$)4*0- @Dao<4*0)#1 onConflict +-*+ -/$ .
$)/# ))*//$*)./#/./$+0'/ 2#/.#*0'#++ )$!/# $). -/*-0+/ - .0'/.
$)1$*'/$*)*!! 2/4+ .*!*)./-$)/.>
I 0)$,0 $) 3<$)'0$)"0+'$/ +-$(-4& 4
I NULL 1'0  $)"+0/$)/* NOT NULL *'0()
I CHECK *)./-$)/J2#$#**(* .)*/.0++*-/+- . )/'4K
**("$1 .4*081 OnConflictStrategy )0(1'0 ./*#**. !-*(!*-4*0-
onConflict +-*+ -/4?#*!/#*. OnConflictStrategy 1'0 .(+./*)
,0$1' )/$/ & 42*-<) #*!/#*. ./-/ "$ .- .0'/.$.$7 - )/ #1$*-
$)$/ ?
135
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Value Meaning
OnConflictStrategy.ABORT
) './#$..// ( )/0/+- . -1 .+-$*-
- .0'/.$)/# /-)./$*))& +./#
/-)./$*)'$1
OnConflictStrategy.FAIL
$& ABORT<0/ +/.+-$*-#)" .4/#$.
.+ $8.// ( )/J ?"?<$!2 !$'*)/# TO/#
-*2/* 0+/ <& +/# #)" ./*/#
+-  $)"SXK
OnConflictStrategy.IGNORE
$& FAIL<0/*)/$)0 .+-* ..$)"/#$.
.// ( )/J ?"?<$!2 !$'*)/# TO/#-*2*0/*!
POO<& +/# #)" ./*/# */# -XXK
OnConflictStrategy.REPLACE
*-0)$,0 ) ..1$*'/$*).< ' / .*/# --*2.
/#/2*0'0. /# 1$*'/$*) !*-  3 0/$)"
/#$..// ( )/
OnConflictStrategy.ROLLBACK *''.&/# 0-- )//-)./$*)
*2 1 -</# 4(4 not 2$)0+2$/#$7 - )/ #1$*-$)**(<0 /*/# 24
/#/**(2*-&.2$/#$/ ?.- .0'/</2**!/# . 81 JFAIL ) ROLLBACKK
#1  ) +- / ?
)/#$.#+/ -<2 2$'' 3($) /#*. 81 *+/$*).). 2#/$/ * .)
2#//# - .0'/$)" 7 /.- $)**(A. ++?.4*02$''. <2#$' /# - -
81 *9$'*+/$*).<! 2 -- +-/$'?
Abort
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.ABORTABORT)
@Update(onConflict = OnConflictStrategyOnConflictStrategy.ABORTABORT)
What SQLite Does
#$../-/ "4(+./* INSERT OR ABORT *- UPDATE OR ABORT .// ( )/.?!
*)./-$)/1$*'/$*)2*0'*0-!-*(/#$..// ( )/</# .// ( )/$..&$++ ?
SQLiteDatabase /#-*2. SQLiteConstraintException?*2 1 -<$!4*0#1 ./-/ 
ROOM AND CONFLICT RESOLUTION
136
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/-)./$*)</#//-)./$*)- ($).*+ )<.*!0-/# -.// ( )/.$)/#
/-)./$*))  3 0/ ?
Effects in Room
)$)$1$0' @Insert *- @Update ( /#*/#/0. . OnConflictStrategy.ABORT 2$''
/#-*2 SQLiteConstraintException $!/# - $.*)./-$)/1$*'/$*)?)$.*'/$*)<
/#$.8/.2$/#2#/4*0($"#/ 3+ /?
# +-*' (*( .2$/# @Transaction?
1 -4( /#*/#/**(" ) -/ .$)- .+*). /*4*0- @DaoA))*// ( /#*.#.
/# .( .$./-0/0- >
@Override
publicpublic void whatever(SomeEntitySomeEntity... entities) {
__db.beginTransaction();
trytry {
// the real work for whatever whatever() does
__db.setTransactionSuccessful();
} finallyfinally {
__db.endTransaction();
}
}
#$.$)'0 . @TransactionA))*// ( /#*.<2#$#%0./2-+/#// (+'/
-*0)''/*4*0-- '( /#*>
@Override
publicpublic void whatever(SomeEntitySomeEntity... entities) {
__db.beginTransaction();
trytry {
supersuper.whatever(entities);
__db.setTransactionSuccessful();
} finallyfinally {
__db.endTransaction();
}
}
!)4/#$)"$)4*0- @Transaction ( /#*/#-*2.) 3 +/$*)<*!)4&$)</#
)/$- /-)./$*)" /.-*'' &<*0-/ .4*!/# tryGfinally ./-0/0- ?
*< 1 )/#*0"# ABORT $..0++*. /*& +/# /-)./$*)*+ )<**(-*''.&
/# /-)./$*)<.*/#/4*0- @Transaction $./*($?
ROOM AND CONFLICT RESOLUTION
137
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Fail
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.FAILFAIL)
@Update(onConflict = OnConflictStrategyOnConflictStrategy.FAILFAIL)
What SQLite Does
#$../-/ "4(+./* INSERT OR FAIL *- UPDATE OR FAIL .// ( )/.?# . 2*-&
(0#'$& /# $- ABORT *0)/ -+-/.<$)/#/ SQLiteConstraintException $./#-*2)<
0//# /-)./$*)- ($).*+ )?
# $7 - ) $.$)2#/#++ ).$!4*0- UPDATE .// ( )/7 /.. 1 -'-*2.?)
/#/. <-*2./#/2 - #)"  prior /*/# *)./-$)/1$*'/$*)- ($)#)" ?
# -*22$/#/# *)./-$)/1$*'/$*)<))4*/# -.!/ -$/<- 0)#)" ?
-)&'4</#$.* .)*/. ('$& +-/$0'-'4"**$ ?/' ./2$/# ABORT<4*0
#1 *).$./ )/ #1$*-?$/# FAIL<.*( -$/--4(*0)/*!/" /.#)" <
)/# - ./$.)*/<)2$/#*0/*$)"4*0-*2)+*./AFAIL )'4.$.<4*0#1 )*$ 
2#//* 3+ /?
Effects in Room
#$' **(0. /*.0++*-/ OnConflictStrategy.FAIL<$/$.)*2 +- / ?
**"' - *(( )./#/4*00. ABORT $)./ ?
Ignore
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.IGNOREIGNORE)
@Update(onConflict = OnConflictStrategyOnConflictStrategy.IGNOREIGNORE)
What SQLite Does
#$../-/ "4(+./* INSERT OR IGNORE *- UPDATE OR IGNORE .// ( )/.?#$.#.
/2*& 4$7 - ) ./*#*2 FAIL 2*-&.>
I /* .)*//#-*2)4.*-/*! 3 +/$*)?
I //-$ ./*+-* .. 1 -4/#$)"/#//# .// ( )/.#*0'7 /?*<$!/# - -
POO-*2./* 0+/ <)/# TO/#-*22$).0+2$/#*)./-$)/
ROOM AND CONFLICT RESOLUTION
138
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
1$*'/$*)</#/-*2$..&$++ <0/$/ *)/$)0 ./*/-4/*+-* ..)4
)*/A4 /A0+/ -*2.?
# - .0'/$./#/ 1 -4/#$)"/#/) $). -/ *-0+/ $.$). -/ *-0+/ <
2$/#$)$1$0'-*2. $)".&$++ 2# - /# 4!$'*)*)./-$)/1$*'/$*).?
#$.$.-$.&4<$)/#/4*0(4)*/)  ..-$'4#1 "**24*!&)*2$)"/#/.*(
*!4*0-- ,0 ./ /()$+0'/$*).$)*//&  7 /?
Effects in Room
$) IGNORE * .)*//-$"" -) 3 +/$*)<**(2$''*(($//# /-)./$*)/#/
*)/$).4*0- @Insert *- @Update 2*-&? ) </#$.2*-&.<$).*!-.**(* .)*/
- % /#)" ./#/$/ 2*0'*/# -2$.  +/ 0. **(-*'' &/#
/-)./$*)?*0./$''.07 -!-*()*/&)*2$)"2#/ 3/'42.#)" 44*0-
@Insert *- @Update ( /#*</#*0"#?
*2 1 -<!*-.// ( )/./#/4*0&)*22$''*)'47 /*) -*2< IGNORE $.
+ -! /'4- .*)' .*'0/$*)?
Replace
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
@Update(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
What SQLite Does
#$../-/ "4(+./* INSERT OR REPLACE *- UPDATE OR REPLACE .// ( )/.?
!$/  )*0)/ -.-*22# -  UNIQUE *- PRIMARY KEY *)./-$)/*):$/.2$/#
/# #)" - ,0 ./ 1$/# INSERT OR REPLACE *- UPDATE OR REPLACE .// ( )/<
$/ deletes the existing data )+-* .2$/#/# .// ( )/?# ) / 7 /$.
/#/4*0- +' /# *'/2$/#/# ) 2/?
!$/  )*0)/ -.-*22# -  NOT NULL *)./-$)/$.1$*'/ <$/2$''// (+//*
- +' /# )0''1'0 2$/#/#  !0'/1'0 !*-/#/*'0()<$!/# - $.*)  8) 
$)/# /' .# (?!)*/</#$../-/ "4 #1 .'$& ABORT?
)!*-)4*/# -*)./-$)/1$*'/$*)</#$../-/ "4 #1 .'$& ABORT?
ROOM AND CONFLICT RESOLUTION
139
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
.- .0'/</#$.$.0. !0'<0/*)'4$)!$-'4*)/-*'' $-0(./) .<)/# )
(*./'4!*- INSERT OR REPLACE?#$."0-)/ ./#/4*0- .$- -*22$''2$)0+
$)/# /' < $/# - 0. $/$.) 2*- 0. $/ " /.-$*!/# +- 1$*0.
$/$*)*!/#/-*2?
Effects in Room
#$../-/ "4<'$& IGNORE<2*-&.+- //4(0#.$/ $)/ ).<!*-/# (*./
*((*)0. . >0+'$/ *) UNIQUE *- PRIMARY KEY *)./-$)/<- .0'/$)"$)
/- +' ( )/?
#$../-/ "42$''#1 /# .( +-*' (.. ABORT $)/# . .2# - $/ #1 .'$&
ABORT H**(2$''-*''&/# /-)./$*)*) $/. ./#
SQLiteConstraintException?
Rollback
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.ROLLBACKROLLBACK)
@Update(onConflict = OnConflictStrategyOnConflictStrategy.ROLLBACKROLLBACK)
What SQLite Does
#$../-/ "4(+./* INSERT OR ROLLBACK *- UPDATE OR ROLLBACK .// ( )/.?
/-*''.&/# /-)./$*)<.4*0($"#/ 3+ /"$1 )/# )( ?
Effects in Room
.2$/# FAIL<2#$' **(0. /*.0++*-/ OnConflictStrategy.ROLLBACK<$/$.)*2
 +- / ?**"' - *(( )./#/4*00. ABORT $)./ ?
What Should You Use with Room?
$1 )/#  +- /$*).<4*0-*+/$*).2$/#**()*2- 1 -4./-$"#/!*-2->
I !4*02)//*- +' /# /. *)/ )/.2$/#) 2/2# )/# - $.
*):$/<0. REPLACE
I !4*02)//*& +/#  3$./$)"/. *)/ )/.2# )/# - $.*):$/<
0/)*//- /$/.) --*-<0. IGNORE
ROOM AND CONFLICT RESOLUTION
140
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I !4*02)//*& +/#  3$./$)"/. *)/ )/.2# )/# - $.*):$/<
)4*02)/) 3 +/$*)/* -$. .*4*0&)*2*0//# +-*' (<0.
ABORT J*-<*)*/#$)"<. ABORT $./#  !0'/./-/ "4K
ROOM AND CONFLICT RESOLUTION
141
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
A Room With a View
$/ <'$& ()4/. .<)*/*)'4.0++*-/. CREATE TABLE !*-- /$)"
/' .<0/ CREATE VIEW !*-- /$)"1$ 2.?)/#$.. </. 1$ 2#.)*/#$)"
(0#/**2$/#))-*$ View?/# -</. 1$ 2$.E1$ 2F*)/**/# -
/$)/# /. ?/(*0)/./*,0 -4/#/4*0)$)/0-),0 -4"$)./
.$!$/2 - /' ?
*- 3(+' < -'$ -$)/# **&<2 .2 AppEntity>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
@Entity(tableName = "apps")
data classdata class AppEntityAppEntity(
@PrimaryKey
valval applicationId: StringString,
valval displayName: StringString,
valval shortDescription: StringString,
valval fullDescription: StringString,
valval latestVersionName: StringString,
valval lastUpdated: LongLong,
valval iconUrl: StringString,
valval packageUrl: StringString,
valval donationUrl: StringString
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM apps")
funfun loadAll(): ListList<AppEntityAppEntity>
@Query("SELECT applicationId, displayName, shortDescription, iconUrl FROM apps")
funfun loadListModels(): ListList<AppListModelAppListModel>
@Insert
funfun insert(entity: AppEntityAppEntity)
}
}
143
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
data classdata class AppListModelAppListModel(
valval applicationId: StringString,
valval displayName: StringString,
valval shortDescription: StringString,
valval iconUrl: StringString
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G++)/$/4?&/K
0. /#//* (*)./-/ #1$)"!0)/$*)- /0-)/4+ */# -/#)/#
)/$/4$/. '!<2# - loadListModels() - /0-)..0. /*!/# *'0().*!/# /' <
)/#*. " /(++ /*) AppListModel $)./ *!) AppEntity?
)*/# -24/*#1 $(+' ( )/ /#/2*0' /* 8) /. 1$ 2*)/#
apps /' /#/#++ )./*- /0-)/#*. !*0-*'0().? )/# ),0 -4/#/1$ 2
.$!$/2 - )/0'/' <2$/#. +-/ @Query !0)/$*).?
Defining a View
0./.) )/$/4$. '- 1$) @Entity ))*//$*)</. 1$ 2$. '- 
1$ @DatabaseView ))*//$*)?//& ./2*+-( / -.>
I value<2#$#$.,0 -4'$& 4*0($"#/#1 $) @Query ))*//$*)<
/#*0"#)*+-( / -.- ''*2 =)
I viewName<2#$#$./#  ,0$1' )/*! tableName *) @Entity<.0++'4$)"/#
)( /*0. !*-/#$.1$ 2
viewName $.*+/$*)'=$!4*0.&$+$/</# 1$ 2$.)( /# .( ./# '..*)2#$#
4*0- ++'4$)"/# @DatabaseView ))*//$*)?
AppView $.1$ 2*)/# AppEntity /' JappsK/#/- /0-)./# !*0-*'0()./#/
2 2 - 0.$)"2$/# loadListModels() ) AppListModel  !*- >
@DatabaseView(
viewName = "appListView",
value = "SELECT applicationId, displayName, shortDescription, iconUrl FROM apps"
)
data classdata class AppViewAppView(
valval applicationId: StringString,
valval displayName: StringString,
valval shortDescription: StringString,
valval iconUrl: StringString
) {
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G++$ 2?&/K
A ROOM WITH A VIEW
144
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Registering the View
0./.2 #1 /*/ #*0- RoomDatabase *0/ )/$/$ .<2 #1 /*/ #$/*0/
/. 1$ 2.?*- )/$/$ .<2 #1 ) entities --4/#/2 .0++'4/*/#
@Database ))*//$*)?*-/. 1$ 2.<2 #1  view --4/#/8''./# .(
-*' >
@Database(
entities = [
AutoGenerateEntityAutoGenerateEntity::classclass,
CompositeKeyEntityCompositeKeyEntity::classclass,
UniqueIndexEntityUniqueIndexEntity::classclass,
IgnoredPropertyEntityIgnoredPropertyEntity::classclass,
NullablePropertyEntityNullablePropertyEntity::classclass,
CustomColumnNameEntityCustomColumnNameEntity::classclass,
IndexedEntityIndexedEntity::classclass,
AppEntityAppEntity::classclass,
AggregateEntityAggregateEntity::classclass,
TransmogrifyingEntityTransmogrifyingEntity::classclass,
EmbeddedLocationEntityEmbeddedLocationEntity::classclass,
DefaultValueEntityDefaultValueEntity::classclass,
com.commonsware.room.misc.onetomany.BookBook::classclass,
com.commonsware.room.misc.onetomany.CategoryCategory::classclass,
com.commonsware.room.misc.manytomany.BookBook::classclass,
com.commonsware.room.misc.manytomany.CategoryCategory::classclass,
com.commonsware.room.misc.manytomany.BookCategoryJoinBookCategoryJoin::classclass,
LinkEntityLinkEntity::classclass,
CommentEntityCommentEntity::classclass,
NoteEntityNoteEntity::classclass,
AutoEnumEntityAutoEnumEntity::classclass
],
views = [
AppViewAppView::classclass
],
version = 1
)
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G$./. ?&/K
*<# - <2 / '' MiscDatabase /#/2 #1 .$)"' @DatabaseView<)(  AppView?
Querying a View
)<%0./.2 )#1  @Dao '../#/2*-&.2$/# AppEntity )$/./' <2 )
A ROOM WITH A VIEW
145
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#1  @Dao '../#/2*-&.2$/# AppView )$/./. 1$ 2>
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.DatabaseViewandroidx.room.DatabaseView
importimport androidx.room.Queryandroidx.room.Query
@DatabaseView(
viewName = "appListView",
value = "SELECT applicationId, displayName, shortDescription, iconUrl FROM apps"
)
data classdata class AppViewAppView(
valval applicationId: StringString,
valval displayName: StringString,
valval shortDescription: StringString,
valval iconUrl: StringString
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM appListView")
funfun loadListView(): ListList<AppViewAppView>
@Query("SELECT * FROM appListView WHERE applicationId = :applicationId")
funfun findById(applicationId: StringString): AppViewAppView?
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G++$ 2?&/K
loadListView() *) AppView.Store 2$''- /0-)/#  3/.( '$./.* .
listListModels() *) AppEntity.Store<./# 4*/#- ! -/*/# .( ,0 -4?
- ! - ) appListView $) @Query ))*//$*).%0./'$& 2 */' .'$& apps?#
1$'' *'0().-  / -($) 4*0- value ,0 -4$)/# @DatabaseView )*0-
+-*+ -/$ .*)/# '../#/2 ++'4 @DatabaseView /*?) -/# *1 -.<2# )2
,0 -4 appListView<$/ 2$''- ''4,0 -4 apps . *)/# value ,0 -4)/# )
.0A. ' /*7*!/#/. *)/# @Query "$)./ appListView?
)'.*#1 ,0 -$ ."$).//# 1$ 2/#/++'4*)./-$)/.<.. )$)
findById()?)/#  )<2 /- //# /. 1$ 2(0#'$& /' @!*-- 
*+ -/$*).? ))*/ @Insert< @Update<*- @Delete /$)/. 1$ 2H!*-
/#*. *+ -/$*).<2 #1 /*()$+0'/ /# /' /#/$.0. 4/# 1$ 2?
OK, Why Bother?
#$.. (.$/.$''4<.2 $)*/"$)(0#4#1$)"/# /. 1$ 2$)./ 
*!%0./#1$)"/# .$(+' loadListModels() !0)/$*)*) AppEntity.Store?
)/. . -1 -</. 1$ 2$(+' ( )//$*).0.0''4#1 /2*! /0- .>
A ROOM WITH A VIEW
146
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I # 4- - A*)'4<)
I # 4) . 0- . +-/ '4!-*(/# 0) -'4$)"/' .
.- .0'/<4*0($"#/ 3+*. /. 1$ 2.. $)".*( /#$)"'$& - A*)'4
/* -/$)/4+ .*!.4./ (.J ?"?<- +*-/" ) -/*-.K/#/*)'4)  ../*
.*( *!/# /?
$/ D.1$ 2.- - A*)'4<0/$/ #.)*+ -A0. -. 0-$/4(* '/# 24/#/
/. . -1 -($"#/?#$.'$($/./# 0. !0') ..*!/. 1$ 2.?
*2 1 -<$/(4 /#//# - -  -/$).0. /.*!*'0().<*- -/$) WHERE
*)./-$)/.</#/4*0) /*0. '*/?/# -/#)0+'$/ /# (-*..()4
. +-/ @Query !0)/$*).<4*0*0' )/-'$5 /# ($)/. 1$ 2?#/24<$!
/# '$./*!*'0().#)" .*-/# WHERE *)./-$)/.) /* - 1$. <4*0)*
.*$)*) .+*/J/# 1$ 2 8)$/$*)K$)./ *!#1$)"/* ).0- /#/4*00+/ '*/.
*!$)$1$0',0 -$ .?
A ROOM WITH A VIEW
147
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room and PRAGMAs
**(*1 -.'*/*!2#/4*02$'') 2# )$)/ -/$)"2$/#$/ !-*(4*0-
++?**(($"#/)*/*1 - everything *!2#/4*02*0''$& /*0. 2$/#$/ <
/#*0"#?
*( /#$)".H+-/$0'-'4)4/#$)"$)1*'1$)"/'  8)$/$*).H+- //4(0#
- ,0$- .**($/. '!/* 0+"- $)*- -/*2*-&?)4/#$)"/#/'$ .*0/.$ *!
**(</#*0"#<$.!$-"( </#*0"#4*0#1 /*- .*-//*'..$$/ ++-*# .
/*(& $/2*-&?
) *!/#*. ++-*# .$./* 3 0/  PRAGMA<.2 2$'' 3+'*- -$ :4$)/#$.
#+/ -?
When To Make Changes
*0#1 /2*($) 1 )/.!*-2# )/*(& #)" .*0/.$ *!**(/*/#
/. >2# )$/$.- / )2# )$/$.*+ ) ?#$#4*00.  + ).*)/#
)/0- *!4*0-#)" .?
#)" ./#/- + -.$./ )/2*0' ++'$ 2# )/# /. $.- / <*-
J 1 )/0''4K1$ Migration 2# )/# /. .# ($.(*$8 ?*- 3(+' <
0.$)" CREATE TRIGGER /*- / /-$"" -- .0'/.$)+ -.$./ )/#)" /*/#
/. <.*4*0*)'4) /**/#$.2# )/# /. .# ($.- / *-
(*$8 ?
*2 1 -<.*( PRAGMA .// ( )/.- /-).$ )/<'$1$)"!*-/# '$! *!*0-*)) /$*)
/*/# /. ?) /# *)) /$*)$.'*. </#  7 /.*!/#*. PRAGMA
.// ( )/."*24?.- .0'/<2 #1 /*++'4/# .  1 -4/$( /#//# /.
$.*+ ) ?
149
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Example: Turbo Boost Mode
*(  1 '*+ -.-  .+ -/ /*2-$)" 1 -4'./$/*!+ -!*-() *0/*!/# $-
/. < 1 )/*/# +*$)/*!-$.&$)"/'*..*-*--0+/$*)?*( PRAGMA
.// ( )/./$ $)/*+ -!*-() /#$.24?
*- 3(+' <)*-(''4<()4/$( .2# )$/ 2-$/ .//*$.&<$/2$''0.
fsync() *-/#  ,0$1' )//*'*&0)/$'''*!/# 4/ .- *)8-( /* 2-$// )?
#$.$.$(+*-/)/$)*+ -/$)".4./ (.2$/#2-$/ A#$)"8' .4./ (.<.*/# -2$.
/# //#/4*0/#$)&/#/4*02-*/ ($"#//0''4%0./ $)07 -2$/$)"/*
 2-$// )$)/# !0/0- ?)-*$<2# )0.$)"/# ext4 8' .4./ (<$.*) .0#?
*2 1 -< PRAGMA synchronous = OFF / ''.$/ /*.&$+/#*. fsync() ''.?#$.
.+ .0+G<2$/#$)- . -$.&*!/# /.  *($)"*--0+/ $!/# - $.
(%*-.4./ (+-*' (2#$' /#/G$."*$)"*)?#$.$./-).$ )/ PRAGMA<*)'4
7 /$)"/# 0-- )/*)) /$*)?
1 )-$.&$ -$. PRAGMA journal_mode = MEMORY?) 7 /</#$..4./*& +/#
/-)./$*)'*"*!/# /. $)( (*-4<-/# -/#)2-$/$)"$//*$.&?. /#
*0( )//$*).// .<E$!/# ++'$/$*)0.$)"$/ -.# .$)/# ($' *!
/-)./$*)2# )/# %*0-)'$)"(* $.. /</# )/# /. 8' 2$''
1 -4'$& '4"**--0+/F?0/<.*( + *+' 2*0'*).$ -+ -!*-() "$).. $)"
1'$/- A*7# - ?#$.$.+ -.$./ )/. //$)"<).*$/*)'4) ./* ++'$ 
*) ?
# ++-*#!*-*/#*!/# . . .$./*0.  RoomDatabase.Callback<.. )$)
/# PragmaTest .(+' (*0' !-*( /# **&D.+-$(-4.(+' +-*% /?
RoomDatabase.Callback 2.$)/-*0 $) /# #+/ -*)/# .0++*-//. ?
#$.$./ ./.A*)'4(*0' <*) /#// ./.$(+*-/$)"0)#*!$/4+*+0'/$*)/
!-*(8' $)/***(A+*2 - /. ''  CityDatabase?
# newInstance() ( /#*/#/2 0. /*- / )$)./) *!*0- CityDatabase
0. . RoomDatabase.Builder .)*-('?*2 1 -<. *) boolean +-( / -<$/
(4'.*0. addCallback() /* RoomDatabase.Callback /*/# 0$' ->
packagepackage com.commonsware.room.pragmatestcom.commonsware.room.pragmatest
importimport android.content.Contextandroid.content.Context
importimport androidx.annotation.VisibleForTestingandroidx.annotation.VisibleForTesting
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
ROOM AND PRAGMAS
150
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.sqlite.db.SupportSQLiteDatabaseandroidx.sqlite.db.SupportSQLiteDatabase
@Database(entities = [CityCity::classclass], version = 1)
abstractabstract classclass CityDatabaseCityDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun cityStore(): CityCity.StoreStore
companioncompanion objectobject {
@VisibleForTesting
constconst valval DB_NAME = "un.db"
funfun newInstance(ctxt: ContextContext, applyPragmas: BooleanBoolean): CityDatabaseCityDatabase {
valval builder = RoomRoom.databaseBuilder(
ctxt,
CityDatabaseCityDatabase::classclass.java,
DB_NAMEDB_NAME
)
ifif (applyPragmas) {
builder.addCallback(objectobject : CallbackCallback() {
overrideoverride funfun onCreate(db: SupportSQLiteDatabaseSupportSQLiteDatabase) {
supersuper.onCreate(db)
db.query("PRAGMA journal_mode = MEMORY")
}
overrideoverride funfun onOpen(db: SupportSQLiteDatabaseSupportSQLiteDatabase) {
supersuper.onOpen(db)
db.query("PRAGMA synchronous = OFF")
}
})
}
returnreturn builder.build()
}
}
}
J!-*( -"( ./G.-G($)G%1G*(G*((*).2- G-**(G+-"(/ ./G$/4/. ?&/K
# - - /2*( /#*./#/4*0).0++'4*) Callback $(+' ( )//$*)>
onCreate() ) onOpen()?./# )( ..0"" ./</# 4- '' 2# )/# /.
$.- / )*+ ) <- .+ /$1 '4?) #<4*0- #) 
SupportSQLiteDatabase $)./) <2#$##.)- ($)$. )/*!/# !-( 2*-&D.
SQLiteDatabase?/#. query() ( /#*/#/2*-&.'$& rawQuery()</&$)"
.$(+' .// ( )/J/#/($"#/- /0-)- .0'/. /K) 3 0/$)"$/?$) PRAGMA
($"#/- /0-)- .0'/. /<2 #1 /*0. query() $)./ *! execSQL()? - <2
$)1*& *0- PRAGMA .// ( )/.//# ++-*+-$/ /$( .?
ROOM AND PRAGMAS
151
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
)<$)/-0/#</# - * .. (/* + -!*-() "$)>
Scenario
Use the
PRAGMAPRAGMAs?
Time
(milliseconds)
). -/$)"P<OUR$/$ .1$$)$1$0'
insert() ''.
* P<PRT
). -/$)"P<OUR$/$ .1$$)$1$0'
insert() ''.
. XUO
). -/$)"P<OUR$/$ .$).$)"' insert()
''
* RXR
). -/$)"P<OUR$/$ .$).$)"' insert()
''
. QTQ
J/ ./.*)0/ *)**"' $3 'SK
-*+ -0. *!/-)./$*).H.0#.*$)"''*!/# $). -/./*) -/# -/#)*)
//$( H#.(0#$"" -$(+/</#*0"#?.$)"/# . /2* PRAGMA .// ( )/.
$.$/'$& 0.$)"#*'* &2$/#/# .! /$ .*7>4*0(4#1 .*( .0'/$ .?
ROOM AND PRAGMAS
152
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Packaged Databases
*!/ )/ )/*/#$)&*!/. . $)"+*+0'/ /-0)/$( >
I -*(0. -$)+0/
I -*(/- /-$ 1 !-*(. -1 -
I -*( 1$ . ).*-.*-*/# -'*'4)($/.*0- .
*2 1 -<)*/# -. )-$*$./*#1 /# /.  +*+0'/ '- 42# )/#
++$.8-./-0)<2$/#.*( $)$/$'/.0++'$ 4/# ++ 1 '*+ -? 2$'' 3+'*-
/#$..*-/*!E+&" F/. $)/#$.#+/ -?
Going Back In Time
&$)/# #+/ -*) !0''A/ 3/. -#$)"<2 )  /. 2$/#.*( /#$)"/*
. -#?# - <2 0. /# / 3/*!?? ''.DE# $( #$) F?# 24/#/2
"*//# /$)/**0-/. 2.>
I  $)/ 3/8' ./#/2 - +&" ... /.
I 0$1$ /#*. 8' .$)/*+-"-+#.<. *)')&'$) ..+-"-+#
. +-/*-.
I ). -//#*. +-"-+#.$)/**0-A )#) /'
$/#$.()0''4*)/# 8-./-0)*!/# ++?0- BookRepository 2*0' / /
/#//# /' 2. (+/4)--)" /*$(+*-//# +-"-+#.?
/2*-&.?/$.'0)&4<0/$/2*-&.?
**(*7 -.)'/ -)/$1 >+&"$)") )/$- $/ /. .).. /? )
/ #**(/*0. /#//. ../-/$)"+*$)/?# )<2# )2 8-./-0)/#
153
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
++<**(2$''*+4/# /. !-*( assets/ $)./ *!- /$)") (+/4
/. 2$/#*0-.# (?*<2 *0'+- +- /. /#/#.*0-+-"-+#.
'- 4$)$/)0. /#/<-/# -/#)*''/# / 3/A8' $(+*-/2*-&/-0)/$( ?
#$.++-*# '$($)/ ./# /$( )* )  ..-4/**/# /$(+*-/?)/#$.
+-/$0'-. </# ++/0''4" /. bigger< 0. /# $/ /. J2$/#$/.
$) 3K$.'-" -/#)/# / 3/8' .?
The Room Mechanics
# PackagedFTS (*0' *! /# **&D.+-$(-4.(+' +-*% / $.'*) *!/# FTS
(*0' < 3 +//#/2 )*2.2$/#/*0.$)"+&" /. ?
)/# *-$"$)' FTS +-*% /<2 # assets/TheTimeMachine/ .$- /*-4<
*)/$)$)". -$ .*!/ 3/8' .- +- . )/$)"#+/ -.$)/# **&?*2<2 #1
assets/TheTimeMachine.db<$/ /. *)/$)$)"*0-+-"-+#.? 2$''
$.0.. '/ -$)/# #+/ - 2# - /#$./. ( !-*(?*-/# (*( )/<..0(
/#/$/2.- / 0.$)" (") /$5 ) ' )./ 4#)?
# )*0- BookDatabase newInstance() !0)/$*)0. . Room.databaseBuilder() /*
. /0+/# /. <2 #1 ) 3/-*)8"0-/$*)''> createFromAsset()?#$.
+-*1$ .- '/$1 +/#2$/#$) assets/ /*/# /. /#/2 2)//*0. //#
*0/. />
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
privateprivate constconst valval DB_NAME = "book.db"
@Database(
entities = [ParagraphEntityParagraphEntity::classclass, ParagraphFtsEntityParagraphFtsEntity::classclass],
version = 1
)
abstractabstract classclass BookDatabaseBookDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun bookStore(): BookStoreBookStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, BookDatabaseBookDatabase::classclass.java, DB_NAMEDB_NAME)
PACKAGED DATABASES
154
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
.createFromAsset("TheTimeMachine.db")
.build()
}
}
J!-*( &" G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&/. ?&/K
*-.$(+' . .</#/$.''/#/2 ) ?
)+-/$0'-< BookRepository )*'*)" -) .''/#/* /#/2 #/*$(+*-/
/# / 3/8' .? )%0./#1 +..A/#-*0"#''./*/# /. >
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
classclass BookRepositoryBookRepository(privateprivate valval db: BookDatabaseBookDatabase) {
suspendsuspend funfun all() = db.bookStore().all()
suspendsuspend funfun filtered(search: StringString): ListList<StringString> =
db.bookStore().filtered(search)
}
J!-*( &" G.-G($)G%1G*(G*((*).2- G-**(G!/.G**& +*.$/*-4?&/K
1 -4/#$)" '. 2*-&..$/$ !*- <)$!4*0-0)/# ++<$/.#*0' #1 .
* ./# *-$"$)'?
J#4$/# FTS (*0' */# -2$/#/# 2#*' $(+*-/A/# A/ 3/A8' .++-*#C
#/#+/ -<)$/..(+' ++<2 - 2-$// ) !*- createFromAsset() 2. 
/***(?K
Creating the Database Asset
!4*0- "*$)"/*0. /#$.++-*#$)4*0-++<4*02$'') /*" //#/+&" 
/. !-*(.*( 2# - ?)<+ -#+.4*0*)*/*2)(") /$5 ) ' *-
.0$/'4A/-$) 0// -:4?)./ <4*0- "*$)"/*) /*0. */# -++-*# ./*
- / /# /. )8''$)4*0-./-/ -/?
Build In Android
# *-$"$)'++-*#0. !*- PackagedFTS 2..$(+' >/# 0/#*--)/# FTS ++
)0.  1$ $' 3+'*- -/**+4/# +*+0'/ /. !-*(/#/++*1 -/*
assets/ *!/# PackagedFTS +-*% /?
PACKAGED DATABASES
155
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
)*/# -2*-.<4*0)- / 4*0-/. 0.$)"*) ++/#/4*0/# )+&"
$)/*)*/# -++?
*- 3(+' <4*0*0'>
I 1 4*0-**( )/$/4'.. .$)'$--4(*0'
I 1 4*0-($)++- ! - ) /#/'$--4(*0'
I 1 0/$'$/4++'.*- ! - ) /#/'$--4(*0'
I 1 /# 0/$'$/4++*)/$)/# * /*+*+0'/ /# /. !-*(.*(
/.*0- .
*02*0'/# )-0)/# 0/$'$/4++)*+4/# /. J0.$)" 1$ $'
3+'*- -< adb pull< /?K/*/# ($)++D. assets/ $- /*-4?
Build By Hand
# - - +' )/4*!$/ '$ )/+-*"-(.1$'' <.0#. -*2. -!*-$/ ?
*0*0'0. *) /*#)A+*+0'/ 4*0-/. ?
)/#$.. <4*02*0''.*) /**).$ -4*0-/. .# (?**( 8) .
2#//# /' .- )2#/.// ( )/.- 0. /*- / /# ()- '/
./-0/0- .J ?"?<$)$ .K?!4*0#1  )'  0/*(/$ 3+*-/$)"*!/.
.# (.</# )4*02$''#1 /# .// ( )/./*0. ?
Build By Script
)*/# -+*..$$'$/4$./*2-$/ .*( .*!/2- /#/- / .)+*+0'/ .4*0-
/. <0/#1 /#/.*!/2- -0)*)4*0- 1 '*+( )/(#$) J*-+ -#+.
.*( . -1 -K<-/# -/#)*)))-*$ 1$ ?$/ '$ )/.- 1$'' !*-
()4')"0" .?*0*0'>
I - / ./)'*) *(()A'$) +-*"-(/#/4*0-0)()0''4
I - / ./)'*) *(()A'$) +-*"-(/#/4*0-0)1$0./*(
-' /.&
I - / -' +'0"$)/#/- / ./# /.
I - / /# /. $- /'4$)0./*(-' /.&
I /?
PACKAGED DATABASES
156
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Dealing With Metadata and Upgrades
1 )/0''4</#*0"#<4*0(4) $7 - )//. < $/# -!*-.# (#)" .*-
*)/ )/#)" .?
*0)#)' /#$.1$ ($"-/$*). .)*-('?*2 1 -<)*/*)'4- 4*0- .+*).$'
!*-#)"$)"/# .# (0/'.*!*-%0./$)"/# *)/ )//*/& /#/) 2.# (
$)/**0)/?)<$!4*02)/) 2*-(*$8 *)/ )/<4*02$'') /*#)' /#/
.2 ''?
)'/ -)/$1 $./* fallbackToDestructiveMigration() /*/#
Room.databaseBuilder() *)8"0-/$*)?#$..4.E$!2 ))*/8)($"-/$*)./*
#)' 1 -.$*)#)" .<./-/*1 -!-*(.-/#F?)<$!4*0- 0.$)"
createFromAsset()</#$.( )./#/**(2$'' ' / /#  3$./$)"/. )
(& ) 2*+4!-*(/# )*2A0-- )/.. /?#$.2*-&.</#*0"#$/#.+-*' (.$!
4*0-/. $.)*/.$(+' *+4*!/# .. /<.2 2$''. $)/# ) 3/. /$*)?
**(0. ./# room_master_table /*/-&**(A.+ $8( //<'*)"2$/#
android_metadata /#/$.. /0+)()" 4/# !-( 2*-&*+4*!
SQLiteDatabase?*-)$)$/$' +'*4( )/<4*0*)*/) /# . /' .H/# *+4
$) PackagedFTS * .)*/#1 /# (<!*- 3(+' ?**(2$''- / /# (
0/*(/$''4?
Hybrid Data
$! $..$(+' $!''4*0-/*( .!-*(+&" /. .<*-$!''4*0-/
*( .!-*(4)($/.*0- .J0. -.<. -1 -.< /?K?#$)"." /( ..42# )4*0-
/$.#4-$*!*/#>+-/$''4+&" )+-/$''44)($?
One Database
*0*0'+0/ 1 -4/#$)"$)/**) /. ?#//. 2*0'./-/*0/ $)"
*+$ !-*().. /<2$/# (+/4/' .!*-/# 4)($/?*0-++2*0'/# )
//*/#*. /' .0.$)"*-$)-4**(*+ -/$*).?
#$.2*-&.<0/+-*'4$/ '$($)/ ./# fallbackToDestructiveMigration()
*+/$*)!*- '$)"2$/#/. .# ()*)/ )/0+"- .?
fallbackToDestructiveMigration() 2*0'./-/ 1 -4/#$)"*1 -!-*(/# )*2A
0-- )/.. /<2#$#2$''*) "$)#1  (+/4/' .!*-/# 4)($/?*0
2*0''*. 2#/ 1 -$.$)/# 0-- )//. D. $/$*)*!/#*. /' .<)/#/
PACKAGED DATABASES
157
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
($"#/(& /# 0. -0)#++4?
)./ <4*02*0') /*0. ($"-/$*)./*- !0''40+/ 4*0-.# ()
!*-( -'4A+&" *)/ )/2#$' ' 1$)"/# 4)($/'*) ?
Separate Databases
) 2*-&-*0)!*-/#$.$./*0. . +-/ /. .>*) !*-/# +&" /)
*) !*-/# 4)($/?*02*0'#1 /2* RoomDatabase '.. .2$/# )/$/$ .<
.<).**)?#$.( )./#/#)" ./#/4*0(& /**) /. <.0#.
- +'$)"$/2$/#/# 0-- )/.. /1$ fallbackToDestructiveMigration()<2*0'
)*/7 //# */# -/. ?
# *2).$ $./#//# . /. .- *(+' / '4$) + ) )/?*0))*/#1
)/$/$ .$)*) /. #1 !*- $")& 4./*/# */# -/. <!*- 3(+' ?
)./ <)4/#$)"'$& /#/2*0') /* $(+' ( )/ $)++'$/$*)* ?
 + )$)"*)4*0-/. ./-0/0- </#$.(4 +-/$'*-$).) ?
Attached Databases
$/ $/. '!#..*'0/$*)!*-/#$.>//# /. .?# ATTACH DATABASE
*(()/ ''.$/ /*2*-&2$/#(0'/$+' /. ./*) <)4*0)#1
.// ( )/./#/+0''!-*(*/#*!/# (?. - !0''4</#$.*0'''*2/#
. (' ..$)/ "-/$*). )$)/# .$)"' A/. .*'0/$*)2#$' ./$''(&$)"$/ .4
/*0'&A- +' /# +&" /0.$)" fallbackToDestructiveMigration()?
**(<#*2 1 -< #.)*.0++*-/!*- ATTACH DATABASE //# +- . )//$( ?
PACKAGED DATABASES
158
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Backing Up Your Room
. -.*)*/'$& $/2# )/# $-/1)$.# .<" /.(" <*-*/# -2$.  *( .
0)0.' ?
*-/0)/ '4<(* -).(-/+#*) #-2- $.!$-'4- '$' ?/$''<+-*' (.)
#++ )?*( /$( ./#*. +-*' (.*( !-*(/# 0. -<.0#.$ )/''4
 ' /$)".*( /#$)"/#//# 42*0'-/# -)*/#1  ' / ?
#$' /# *7 -.E&0+.F<$)- '$/4)-*$D.E&0+F( #)$.($.
(--..$)">
I  $/# -0. -.)*- 1 '*+ -../-$/'4*)/-*'2# )&0+.- /&$)"+'
I  $/# -0. -.)*- 1 '*+ -.*)/-*'2# - &0+.- ./*- 
I  $/# -0. -.)*- 1 '*+ -.*)/-*'2# )&0+." /- ./*-
).#*-/<)-*$D.E&0+F( #)$.($.!*-$../ -- *1 -4J ?"?<0. --*++ 
/# $-+#*) $)/*$' /)- +' /# +#*) K?*0(42)//**7 -.*( /#$)"
'. /#/$.(*- 0. -A*)/-*'' ?#$.#+/ -2$'' 3+'*- #*2/**/#/?
Backup and Restore. Or, Import and Export.
E&0+F)E- ./*- F$(+'4(&$)"*+4*!/# /<.0#/#/$). *!
+-*' (2$/#/# *-$"$)'/<2 )- +' /# (" *-$"$)'2$/#/# *+4?
E(+*-/F)E 3+*-/F$(+'4(&$)"*+4*!/# /<.0#/#//# 0. -)
+ -#+.()$+0'/ /#//$).*( /#$-A+-/4/**'?/ -</# E$(+*-/F$(+'$ .
/#/2 )/&  3/ -)'/)$//*/# ++D.*2)/<*-+ -#+.- +'
/# ++D.*2)/*0/-$"#/?
159
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# '$) $1$$)"E&0+G- ./*- F)E$(+*-/G 3+*-/F$.-/# -'0--4?!4*0&
0+//*0. -A ..$' .+*/<)/# /$.$))*+ )!*-(/<E&0+F)
E 3+*-/F *( $ )/$'<$) 7 /?*0(4)*/ intend !*-/# 0. -/*0. /# /
$))*/# -+$  *!.*!/2- <0/4*0))*/./*+$/ $/# -?
Choosing a Storage Target
*0*0'&0+*- 3+*-//# //*.*( 8' *)/# 8' .4./ (?)) 2 -
1 -.$*).*!)-*$</#*0"#<4*0-$'$/4/*2*-&2$/# 3/ -)'./*-" ./-/./*
 *( (*- - ./-$/ ?
*0*0'&0+*- 3+*-//# //*.*( '*/$*)/#//# 0. -#**. ./#-*0"#
/# /*-"  ..-( 2*-&<.0#.1$ ACTION_CREATE_DOCUMENT?
*0*0'&0+/# //*.*( . -1 -?)/#$.. </# .! .//#$)"/**$./*
&0+/# //*.*( 8' *)/# 8' .4./ (8-./</# )0+'*/# 8' ?.2 2$''
. '/ -$)/# #+/ -<$/$.$(+*-/)/!*-/# /. /* '*. //# /$( 4*0
- (&$)"/# &0+*+4<.*2 2)//# &0++-* ../* .!./.+*..$' ?
$- /'4./- ($)"/# //*.*( . -1 -2$'' '*/.'*2 -/#)(&$)"'*'
*+4<0 /*) /2*-&.+ .?
/(4 /#/4*0-- +*.$/*-4) *'$1$*0.J.*( 2#/K/*/# .  $.$*).?!4*0
/ ''/# - +*.$/*-4/*&0+/* Uri</# )2# /# -/#/ Uri +*$)/./*'*'8' *-
/*'*/$*)!-*( ACTION_CREATE_DOCUMENT $.$((/ -$'?*0-)1$ 2(* '
)(& /#  $.$*)*! 3/'42# - /# *+4$.( ?
Thinking About Journal Modes
!4*0 3($) 4*0-**(A )' ++D.+*-/$*)*!$)/ -)'./*-" $) 1$ $'
3+'*- -<$)/# databases/ .0$- /*-44*02$''8)4*0-/0'**(/. ?
- ,0 )/'4</#*0"#<$/*).$./.*!/#- 8' .>
I # /0'/. Jwhatever.dbK
I 8' 2$/# -shm ++ ) Jwhatever.db-shmK
I 8' 2$/# -wal ++ ) Jwhatever.db-walK
#$.$)$/ ./#//# /. 2.*+ ) $)+-/$0'-E%*0-)'(* F'' 
2-$/ A# '*""$)"JK )#.)*/ )'*. ?*0"#'4.+ &$)"<$)
(* </. /-)./$*).+-$(-$'47 //# . . *)-48' .)*)'4" /
( -" $)/*/# ($)/. /+-/$0'-E# &+*$)/.F?0## &+*$)/.*0-
BACKING UP YOUR ROOM
160
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
0/*(/$''4<)!*-(*./$-0(./) .2 *)*/- ''42*--4*0//# (?
*2 1 -<2# )$/*( ./*(&$)"&0+*- 3+*-/$)"/. <$/$. ./$!
1 -4/#$)"" /.( -" $)/*/# ($)/. 8' ?
-$(-$'4</# 242 )*/#/$./* close() *0- RoomDatabase?) ''
*)) /$*)./**0-$/ /. - '*. <$/ 2$''# &+*$)//# /. <
( -"$)" 1 -4/#$)"$)/*/# ($)/. 8' )- (*1$)"/# -shm ) -wal
8' .?
*0)$.' *0/-$"#/4''$)" setJournalMode() *)4*0-
RoomDatabase.Builder )*+/$)"$)/* TRUNCATE .%*0-)'(* ?
Keeping It Closed
*2 1 -< 1 )$!4*02 - /**+/*0/*!<$/$.$(+*-/)//#/4*0-/. 
'*. 2# )2 - (&$)"&0+*- 3+*-/$)"/# /?$/ 2$''&)*2
)*/#$)"*0//#$.&0+G 3+*-/*+ -/$*)?!+-/*!4*0-* $.+ -!*-($)"
/. *+ -/$*).2#$' )*/# -+-/*!4*0-* $.(&$)"*+4*!/#
/. 8' <4*02$''2$)0+2$/#*--0+/ /. *+4?#$.$.)*/0)$,0 /*
$/ >(&$)"*+4*!8' 2#$' /#/8' $. $)"2-$// )/*$..&$)"!*-/-*0' ?
)+-$)$+' </#$.$.)*//**#-/*()" >
I  !*- (&$)"/# *+4*!/# /. <'' close() *)/# RoomDatabase
I & /# *+4
I  !*- /-4$)"/*2*-&2$/#/# /. "$)<- A*+ )$/0.$)"
RoomDatabase.Builder
#$.(& .- +*.$/*-4$/(*- *(+'$/ <.)*2$/2$'') /* ' /*
'*. )*+ )/. ..)  ?'0.<4*0-- +*.$/*-42$'' 4*0-+-$(-4E'$)
*! ! ). F"$)./0".?*- 3(+' <.0++*. /#/4*0#1  WorkManager
.# 0' /.&/*.4)#-*)$5 4*0-++2$/# . -1$ ?!/#//.&" /.
3 0/ 2#$' 4*0-&0+$. $)"( <4*0*)*/2)//*- A*+ )/# /.
//#//$( ?*( #*2</# Worker 2$'') /*8)*0//#//# /. $.
0)1$'' <.*$/)- .# 0' /#/2*-&?
# .(+' ++< $)".$(+' **&.(+' <* .)*/" /$)/*#*2/*()" /#$.<
./#  /$'.2$''1-42$ '44++?
BACKING UP YOUR ROOM
161
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Import and Export Mechanics
# ImportExport (*0' *! /# **&D.+-$(-4.(+' +-*% /  (*)./-/ .
*+4$)"/. .!*-&0+G- ./*- *-$(+*-/G 3+*-/+0-+*. .?
/.*).$./.(*./'4*!/#- $"0//*).>
I .*( -)*(//*/# /.
I 3+*-//# /. /*'*/$*)/#//# 0. -#**. .1$
ActivityResultContracts.CreateDocument
I (+*-//# /. !-*('*/$*)/#//# 0. -#**. .1$
ActivityResultContracts.OpenDocument
'.*<//# *//*(</# - $. TextView .#*2$)"/# )0( -*!-*2.$)*0-*)
/. /' )#*2*'/# *' ./-*2$.?
RandomRepository $./# - +*.$/*-4/#/)*/*)'4( $/ .*)1 )/$*)'/.
*+ -/$*).0/'.*#)' .$(+*-/) 3+*-/*+ -/$*).>
packagepackage com.commonsware.room.importexportcom.commonsware.room.importexport
importimport android.content.Contextandroid.content.Context
importimport android.net.Uriandroid.net.Uri
importimport kotlinx.coroutines.CoroutineScopekotlinx.coroutines.CoroutineScope
importimport kotlinx.coroutines.asCoroutineDispatcherkotlinx.coroutines.asCoroutineDispatcher
importimport kotlinx.coroutines.sync.Mutexkotlinx.coroutines.sync.Mutex
importimport kotlinx.coroutines.sync.withLockkotlinx.coroutines.sync.withLock
importimport kotlinx.coroutines.withContextkotlinx.coroutines.withContext
importimport java.util.concurrent.Executorsjava.util.concurrent.Executors
importimport kotlin.random.Randomkotlin.random.Random
classclass RandomRepositoryRandomRepository(
privateprivate valval context: ContextContext,
privateprivate valval appScope: CoroutineScopeCoroutineScope
) {
privateprivate valval mutex = MutexMutex()
privateprivate valval dispatcher =
ExecutorsExecutors.newSingleThreadExecutor().asCoroutineDispatcher()
privateprivate varvar db: RandomDatabaseRandomDatabase? = nullnull
suspendsuspend funfun summarize() =
withContext(dispatcher) {
mutex.withLock {
ifif (RandomDatabaseRandomDatabase.exists(context)) {
BACKING UP YOUR ROOM
162
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
db().randomStore().summarize()
} elseelse {
SummarySummary(count = 0)
}
}
}
suspendsuspend funfun populate() {
withContext(dispatcher + appScope.coroutineContext) {
mutex.withLock {
valval count = RandomRandom.nextInt(100) + 1
db().randomStore().insert((1..count).map { RandomEntityRandomEntity(0) })
}
}
}
suspendsuspend funfun export(uri: UriUri) {
withContext(dispatcher + appScope.coroutineContext) {
mutex.withLock {
db?.close()
// ensure no more access and single database file
db = nullnull
context.contentResolver.openOutputStream(uri)?.use {
RandomDatabaseRandomDatabase.copyTo(context, it)
}
}
}
}
suspendsuspend funfun import(uri: UriUri) {
withContext(dispatcher + appScope.coroutineContext) {
mutex.withLock {
db?.close()
// ensure no more access and single database file
db = nullnull
context.contentResolver.openInputStream(uri)?.use {
RandomDatabaseRandomDatabase.copyFrom(context, it)
}
}
}
}
privateprivate funfun db(): RandomDatabaseRandomDatabase {
ifif (db == nullnull) {
db = RandomDatabaseRandomDatabase.newInstance(context)
}
BACKING UP YOUR ROOM
163
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
returnreturn db!!
}
}
J!-*( (+*-/3+*-/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G)*( +*.$/*-4?&/K
.2$/#.*( *!/# */# - 3(+' .</#$..(+' 0. .*/'$)*-*0/$) .?
RandomRepository " / Context ) CoroutineScope $)% / 1$*$)H/#
Context $./**+ ) RandomDatabase<2#$' /# '// -$.!*- ).0-$)"/#//
(*$8/$*)*+ -/$*).*)*/" /) ' . *)*0- MainActivity " //$)"
 ./-*4 ?
0)/$*).'$& summarize() J0. /*" //# /!*-/# TextViewK) populate()
J0. /*$). -/.*( -)*(-*2.$)/*/# /. /' K- (*./'4)*-('?# 4
#1 /2*$7 - ) .*(+- /*+./.(+' .>
P? */#2-+/# /. G$) Mutex? Mutex $./# *-*0/$) .++-*#!*-
(0/0' 3'0.$*)?)'4*) $/*!* ) *+ -/$)"$).$ *!/# Mutex
J)$/. withLock() !0)/$*)K//$( ? 0. /#$. Mutex $)*0- import()
) export() !0)/$*)..2 ''<)2 0. /#$./* ).0- /#/)*/#$)"/-$ .
2*-&$)"2$/#*0-/. 2#$' /# $(+*-/*- 3+*-/*+ -/$*).-
*)"*$)"?#$.$.-0 ++-*#<*7 -$)"'$($/ *)0-- )4<)(*-
.*+#$./$/ ++($"#/2)//**.*( /#$)"@2 ''<(*- .*+#$./$/ ?
Q? */#'' db() !0)/$*)/*" //# RandomDatabase $)./) /*0. ? db()<$)
/0-)<'54A- / ./# RandomDatabase<$!$/+- . )/'4$. null?
import() ) export() '.*0. /# Mutex?*2 1 -<$).$ *! withLock()</# 4>
I close() *0- RandomDatabase<.**0-8' ." /' - )2 #1 
.$)"' /. 8'
I  //# db +-*+ -/4/* null<.* db() &)*2./*- A*+ )/# /. *)/#
) 3/- "0'-$/*!/. G
I *+$ ./# /. /**-!-*('*/$*).+ $8 4 Uri<0.$)"
copyFrom() ) copyTo() !0)/$*).*) RandomDatabase /*#)' /# /0'
*+4*+ -/$*).>
packagepackage com.commonsware.room.importexportcom.commonsware.room.importexport
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
importimport java.io.InputStreamjava.io.InputStream
importimport java.io.OutputStreamjava.io.OutputStream
BACKING UP YOUR ROOM
164
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
privateprivate constconst valval DB_NAME = "random.db"
@Database(entities = [RandomEntityRandomEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass RandomDatabaseRandomDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun randomStore(): RandomStoreRandomStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, RandomDatabaseRandomDatabase::classclass.java, DB_NAMEDB_NAME).build()
funfun exists(context: ContextContext) = context.getDatabasePath(DB_NAMEDB_NAME).exists()
funfun copyTo(context: ContextContext, stream: OutputStreamOutputStream) {
context.getDatabasePath(DB_NAMEDB_NAME).inputStream().copyTo(stream)
}
funfun copyFrom(context: ContextContext, stream: InputStreamInputStream) {
valval dbFile = context.getDatabasePath(DB_NAMEDB_NAME)
dbFile.delete()
stream.copyTo(dbFile.outputStream())
}
}
}
J!-*( (+*-/3+*-/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G)*(/. ?&/K
The createFromFile()createFromFile() Alternative
) ) -'$ -#+/ -<2  3+'*-  createFromAsset() .24/*. /0+**(
/. !-*() 3$./$)"/. *+4<$)/#$.. +&" .).. /?
# - $.'.* createFromFile()?#$.2*-&.%0./'$& createFromAsset()<0/$/
/& . File +-( / -/#/.#*0'+*$)//*- ' 8' *)/$)$)"/# /.
*+4?
*-- ./*-$)"!-*(&0+*-$(+*-/$)"!-*(/. </#$.$.(*- *)1 )$ )/
/#)$./# ()0'A*+4A!-*(AA./- (++-*#0. $)/#$.#+/ -D..(+' ?
*2 1 -<$/*)'42*-&.2$/#8' .<2#$#$.)0)!*-/0)/ '$($//$*)?# .(+'
0. . Uri<)*/ File</*$ )/$!4/# '*/$*)*!/# &0+<).* createFromFile()
$.)*/)1$'' '/ -)/$1 ?
BACKING UP YOUR ROOM
165
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
SQLite Clients
*( /$( .<$/2*0' )$ /*'**&/2#/$.$)*0-**(A+*2 - /. ?*-
3(+' <$/($"#/.$(+'$!4/-&$)"*2)0"$!2 *0'+ &$).$ *0-/' .
). 2#//$.$).$ *!/# (?
*-/#/<4*02$'') .*( !*-(*!$/ '$ )/?
*-/0)/ '4<$/ $.*+ ).*0- )#. )-*0)!*-'*)"/$( ?# - -
,0$/ ! 2'$ )/.1$'' /*4*0/*#**. !-*(</*8)*) /#/( /.4*0-) .
)8/.4*0-2*-&:*2?
Database Inspector
' $)")$/ <./-/$)"2$/#)-*$/0$*S?P<$.)-*$/0$*$/. '!<
/#-*0"#$/./. ).+ /*-/**'?
Getting To Your Database
*24*0" //*/# /. ).+ /*-1-$ .4)-*$/0$*1 -.$*)>
167
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I *-S?P?3)S?Q?3<'**&!*-/# E/. ).+ /*-F/**'<4 !0'/*& 
*)/# *//*( "
I *--/$*3<'**&!*-/# E++).+ /$*)F/**'<4 !0'/*& *)/#
*//*( " HE/. ).+ /*-F$./$)/# - .0'/$)"
Figure 5: Android Studio App Inspection Tool, Showing the Database Inspector
)/# ./-$+%0./ '*2/# /$/' <4*02$''. /# +-/$0'-++/#//.
).+ /*-$.*7 -$)"/*$).+ /<*-E ' /-* ..F$!/. ).+ /*-* .)*/
&)*2*!.0$/' +-* ..J ?"?<4*0-+#*) $.)*/+'0"" $)K?*0).2$/#/*
.*( /#$)" '. 4'$&$)"*)/#/ )/-4)#**.$)"/#  .$-  1$ J*-
(0'/*-K)+-* ..!-*(-*+A*2)( )0.?
*/ /#/'*$)"/# /.  /$'.. (./*/& !-'*)" -/#)$/.#*0'H
+/$ )/B
/. ).+ /*-++ -./*'**&!*-/. .$)/# ./*&'*/$*)/#/**(
)(*./*/# -++.+' /# (H$)/#$.. <$/.#*2. stuff.db $)/# /- *)/#
' !/?).$ <$/.#*2./2*/' .>
I todos<&$)/*/# *) .$)1-$*0..(+' .$)/#$.**&
I room_master_table</' - / 4**($/. '!/*# '+()" $/.2*-&
SQLITE CLIENTS
168
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Seeing the Schema
# /- "* . 4*)/# /. )/# /' .$)/#//. ?*'$)"*+ )
/' "$1 .4*0/# /' D..# (>
Figure 6: Database Inspector, Showing Table Schema
Performing Operations
)/# /**'-*1 /#//- </# /**'-0//*)/#/'**&.'$& "-$2$/#
(")$!4$)""'..2$''*+ )/!*-4*0/* ' /* 3 0/ ,0 -$ ."$).//#
. ' / /. >
Figure 7: Database Inspector, With Query Toolbar Button Highlighted
SQLITE CLIENTS
169
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# -*+A*2)*)/-*'./# /. <)/# 8 '*1 $/$.2# - 4*0) )/ -
 3+- ..$*)?'$&$)"/# E0)F0//*)/# ) 3 0/ .4*0- 3+- ..$*)<2$/#
"-$.#*2$)"4*0/# - .0'/.>
Figure 8: Database Inspector, Showing Query, Results, and Congratulatory Tooltip
Using Your DAO
# /. ).+ /*-'.*/$ .$)/*/# )-*$.*0-  $/*-?*-.*( ,0 -$ .*)
4*0-'.. .<4*0)0. "0// -$*)/*/-$"" -/# .( ,0 -4/*-0)$)/#
/. ).+ /*->
Figure 9: Android Studio Editor, Showing Query Gutter Icons, Plus
all()
Results in
Database Inspector
SQLITE CLIENTS
170
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#*. $*).2$''*)'4++ -2#$' /# /. ).+ /*-$.-0))$)")$.
$).+ /$)"4*0-++D./. ?
Updating the Output
+*+0'-/#$)"/**2$/#/. .$./*#)" /# /?
!4*0#)" /# /2$/#$)/# ++<4*0#1 /2*24.*!. $)"/#*. - .0'/.
- : / $)/# /. ).+ /*-*0/+0/?
# '*2A$(+/++-*#$..$(+'4/*'$&/# E !- .#/' F0//*)*1 4*0-
,0 -4*0/+0/J)*/ >/# E/' F- ! -- /*$)0//*)+/$*)$./# /' <)*/
/. /' K?#$.2$''- A,0 -4/# /. )0+/ /# *0/+0//*(/#?
# '/ -)/$1 $./*# &/# E$1 0+/ .F# &*3>
Figure 10: Database Inspector, With “Live updates” Checkbox Highlighted
#$.2$''#**&$)/*/# .( E$)1'$/$*)/-& -F/#/**($/. '!0. ./*&)*2/*
 '$1 -) 20+/ ./*4*0-++1$ LiveData< Flow< /?# /' .#*2$)"/#
,0 -4- .0'/.2$''0+/ $)) -A- 'A/$( .4*0-++(& .#)" ./*/# /?
Updating the App
# )E$1 0+/ .F$.# & <( .." ++ -. '*2/# *0/+0/>E .0'/.-
- A*)'4F?*02$''. /# .( ( .." 2# )-0))$)"*) *!4*0-!0)/$*).<
*-$!4*0-0)0./*(,0 -44*0-. '!?
*2 1 -<$!4*0%0./*0' A'$&/# /' )( $)/# /- *)/# ' !/</#/( .."
* .)*/++ -?)<$)/#/. </# /' $.$/. '!'$1 H)4#)" ./#/4*0
(& /# - 2$'' - : / <$)) -A- 'A/$( <$)4*0-++D.?0./*0' A'$&*)
 ''</4+ $)/# - 1$. 1'0 <)+- .. Enter *- Return ?
#$./**/$ .$)/***(D.$)1'$/$*)/-& -?# /#)" /#/4*0(&
SQLITE CLIENTS
171
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/-$"" -./#//-& -)$)/0-)0. .)4*!4*0-- /$1 *. -1 -./*" /!- .#
/!-*(**(<- : /$)"2#/ 1 -#)" 4*0(& ?
*/ </#*0"#</#//# /. ).+ /*-* .)*/+ -!*-((0#/1'$/$*)?
*- 3(+' < Boolean +-*+ -/4(+./*) INTEGER *'0()$)/# /' <2$/# 0
( )$)" false ) 1 ( )$)" true@0//. ).+ /*-2$'' #++4/*' /
4*08''$) 3 ./# 1'0 ?)<.$) $)$/ *'0()/4+ .- #$)/.</.
).+ /*-2$'' #++4/*' /4*08''$) foo ./# 1'0 *!) INTEGER *'0()?
#$' /. ).+ /*-$.#++4<**(($"#/)*/ <.* - !0'2# )
(*$!4$)"4*0-++D./1$/# ).+ /*-/#$.24?
DB Browser for SQLite
#$' /# - - 1-$ /4*!./)'*) $/ '$ )/.<$)/ -(.*! .&/*+0. < 
-*2. -!*-$/ $.-"0'4/# (*./+*+0'-?/$.*+ ).*0- <1$'' !*-
$)03G(G$)*2.<)$.!$-'4 .4/*0. ?
Copying Your Database
/#.)*.+ $8$)/ "-/$*)2$/#)-*$*-)-*$/0$*</#*0"#<2#$#( ).
/#/4*02$'') /**+44*0-/. *7*!4*0- 1$ *- (0'/*-)*)/*
4*0- 1 '*+( )/(#$) ?
 ''4<4*0-++D.+-* ..$./ -($)/ 2# )4*0*/#$.<.*4*0-++* .)*/
// (+//*0. /# /. 2#$' /# *+4*+ -/$*)$.*)"*$)"?
!4*0- 0.$)"*-$)-4**(<4*0-/. 2$'' $)/#  !0'/'*/$*)!*-
$/ /. .!*-4*0-++?-*(/# ./)+*$)/*! 1 '*+( )//**'.<!*-/#
+-$(-4 1$ 0. -</#/2$'' >
/data/data/.../databases/
@2# - ... $.4*0-++'$/$*)?
)/# - <4*02$''8)/. 8' <2$/#/# )( /#/4*0"1 $/$)4*0-
RoomDatabase J ?"?< stuff.dbK?-/$0'-'4$!/# ++*+ ) /# /. )$
)*/ 3+'$$/'4'*. $/<4*02$'''.*. /2*$/$*)'8' .<2$/#/# .( )( .
/# /. +'0. -shm ) -wal 3/ ).$*).?*02$'') /**+4''*!/# . 8' ./*
4*0- 1 '*+( )/(#$) <(*./'$& '40.$)" 1$ $' 3+'*- -!-*()-*$
/0$*?
SQLITE CLIENTS
172
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*0)/# )*+ )$/$)-*2. -!*-$/ 0.$)"/# E+ )/. F/**'-
0//*)<. ' /$)"/# /. 8' $/. '!J)*//# -shm *- -wal 8' .<$!)4K?
Basic Database Operations
$& /. ).+ /*-<-*2. -!*-$/ "$1 .4*0/- *!/# 1-$*0./' .
$)/# E/. /-0/0- F/<2# - 4*0). /# .# (!*-/' >
Figure 11: DB Browser for SQLite, Showing Table Schema
# E-*2. /F/"$1 .4*0/0'-1$ 2*!/# *)/ )/.*!. ' / /' <
#*. )1$/# -*+A*2)$)/# /D.*2)/**'->
Figure 12: DB Browser for SQLite, Showing Table Contents
# E3 0/ F/' /.4*0 )/ -$)4*0-*2),0 -$ .*-*/# -*+ -/$*).J ?"?<
INSERT .// ( )/.K)-0)/# ("$)./4*0-/. ?*-,0 -$ .*-*/# -
.// ( )/./#/- /0-)- .0'/.<4*0" //' .#*2$)"/#*. - .0'/.>
SQLITE CLIENTS
173
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*/ </#*0"#</#/$!4*0(*$!4/# /)2$.#/*+ -.$.//#*. #)" .<4*0
) /*'$&/# E-$/ #)" .F/**'-0//*)?
# )4*0- *) <$!4*0'$&/# E'*. /. F0//*)</# $/ /.
2$'' '*. ' )'4<' 1$)"4*02$/#%0.//# /. 8' )2$/#*0/)4 -shm *-
-wal 8' ?
!4*02$.#<4*0*0'/# )*+4/# /. &/*/#  1$ <0.$)" 1$ $'
3+'*- -?*2 1 ->
I  .0- /*/ -($)/ 4*0-++D.+-* .. !*- 4*0*/#$.<.*4*0*)*/
- +' $/ 8' . #$)**(D.&
I !4*0-++D./#. -shm )G*- -wal 8' .<)4*00. E'*. /. F
/*" /' ).$)"' A8' *+4*!4*0-/. <$)$/$*)/**+4$)"/#/
/. /*4*0- 1$ <4*02$'') /*- (*1 /#  1$ D. -shm ) -wal
8' ./*(/#
Flipper
)*/# -+*..$$'$/4$./*$)/ "-/ '$++ -?'$++ -$.'$--4!-*( **&/#/
4*0)$)/ "-/ $)/* 0"0$'.*!4*0-++?/*+ ).+*-//#/'$++ -A
.0++'$  .&/*+++)*)) //*? + )$)"*)2#/'$++ -+'0"$).4*0#1
)' </#/ .&/*+++)*$7 - )//#$)".@2$/#*)  $)""$1$)"4*0
/. ).+ /*-A./4'  ../*4*0-++D./. ?
# PagedFTS (*0' *! /# **&D.+-$(-4.(+' +-*% / H+-*8'  '. 2# - $)
/# **& H#++ )./*$)/ "-/ '$++ -?
Adding Dependencies
'$++ -/& .)++-*#0. 4! 2 0""$)"A )/-$'$--$ .>#1 .*( - '
 + ) )$ ./*/* debug 0$'.)E)*A*+F + ) )4/*/* release
0$'.?# *% /$1 $./*''*2!*-*)8"0-/$*)/* /# .( - "-' ..*!0$'
/4+ <2#$' 1*$$)"/# -$.&*!.#$++$)" 0"A- '/ * /*4*0-0. -.?
PagedFTS</# - !*- <#./2* debugImplementation '$) .)*)
releaseImplementation '$) $)$/. dependencies '$./>
SQLITE CLIENTS
174
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
debugImplementation 'com.facebook.flipper:flipper:0.96.1'
debugImplementation 'com.facebook.soloader:soloader:0.10.1'
releaseImplementation 'com.facebook.flipper:flipper-noop:0.96.1'
J!-*( " G0$'?"-' K
Configuring Flipper for Database Debugging
'$++ -$. .$") /* *)8"0- !-*( onCreate() *!0./*( Application '..?
PagedFTS #..0#'..<)(  KoinApp>
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport android.app.Applicationandroid.app.Application
importimport com.facebook.flipper.android.AndroidFlipperClientcom.facebook.flipper.android.AndroidFlipperClient
importimport com.facebook.flipper.android.utils.FlipperUtilscom.facebook.flipper.android.utils.FlipperUtils
importimport com.facebook.flipper.plugins.databases.DatabasesFlipperPlugincom.facebook.flipper.plugins.databases.DatabasesFlipperPlugin
importimport com.facebook.flipper.plugins.inspector.DescriptorMappingcom.facebook.flipper.plugins.inspector.DescriptorMapping
importimport com.facebook.flipper.plugins.inspector.InspectorFlipperPlugincom.facebook.flipper.plugins.inspector.InspectorFlipperPlugin
importimport com.facebook.soloader.SoLoadercom.facebook.soloader.SoLoader
importimport org.koin.android.ext.koin.androidContextorg.koin.android.ext.koin.androidContext
importimport org.koin.android.ext.koin.androidLoggerorg.koin.android.ext.koin.androidLogger
importimport org.koin.androidx.viewmodel.dsl.viewModelorg.koin.androidx.viewmodel.dsl.viewModel
importimport org.koin.core.context.startKoinorg.koin.core.context.startKoin
importimport org.koin.dsl.moduleorg.koin.dsl.module
classclass KoinAppKoinApp : ApplicationApplication() {
privateprivate valval koinModule = module {
single { BookDatabaseBookDatabase.newInstance(androidContext()) }
single { BookRepositoryBookRepository(getget()) }
viewModel { BookViewModelBookViewModel(getget()) }
viewModel { (search: StringString) -> SearchViewModelSearchViewModel(search, getget()) }
}
overrideoverride funfun onCreate() {
supersuper.onCreate()
startKoin {
androidLogger()
androidContext(thisthis@KoinApp)
modules(koinModule)
}
ifif (BuildConfigBuildConfig.DEBUGDEBUG && FlipperUtilsFlipperUtils.shouldEnableFlipper(thisthis)) {
SoLoaderSoLoader.init(thisthis, falsefalse)
SQLITE CLIENTS
175
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
AndroidFlipperClientAndroidFlipperClient.getInstance(thisthis).also { client ->
client.addPlugin(DatabasesFlipperPluginDatabasesFlipperPlugin(thisthis))
client.start()
}
}
}
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G*$)++?&/K
-/*! onCreate() $.. //$)"0+*$)!*- + ) )4$)1 -.$*)?*2 1 -</# './
! 2'$) .*! onCreate() - !*0. *)'$++ -$)$/$'$5/$*)>
ifif (BuildConfigBuildConfig.DEBUGDEBUG && FlipperUtilsFlipperUtils.shouldEnableFlipper(thisthis)) {
SoLoaderSoLoader.init(thisthis, falsefalse)
AndroidFlipperClientAndroidFlipperClient.getInstance(thisthis).also { client ->
client.addPlugin(DatabasesFlipperPluginDatabasesFlipperPlugin(thisthis))
client.start()
}
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G*$)++?&/K
# & 4<!*- $)"' /* 0"/. .0.$)"'$++ -<$.$)"/#
DatabasesFlipperPlugin /*'$++ -?'$++ -*+ -/ .. *). -$ .*!+'0"$).<
)/#$.*) +-*1$ . ../*$/ /. .</' .//#*. $)./)-
'*/$*).? # *0( )//$*)!*-/#/+'0"$) +-*1$ ..*( */# -*+/$*).<
+-/$0'-'4!*-/. .- .$$)"$))*)A./)-'*/$*).?
/# '$++ -*0( )//$*) !*-(*-  /$'.*0//#$.$)$/$'$5/$*)G
*)8"0-/$*)+-* ..?
Obtaining and Using the Desktop App
# '$++ -.$/ #. *2)'**+/$*).!*-$)03<(<)$)*2. 1 -.$*).*!
/#  .&/*+++?$)*2.)$)03- - 8' .*)/$)$)"/#  //$1
++=(" /.+-*+ -8' ?
SQLITE CLIENTS
176
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
!4*0-0)4*0-'$++ -A )' ++</# )-0)/# '$++ - .&/*+++<4*0-++2$''
++ -$). /$*)$)/# *-$*)*)/# ' !/*!/#  .&/*+++<)4*0)
)' /# /. ./**'1$.2$/#>
Figure 13: Flipper, Showing PagedFTS App and Databases Tool
SQLITE CLIENTS
177
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*0)#**. /# /. )/' 1$-*+A*2)./*2-./# /*+*!/#
/. .*)/ )/+) ?#  !0'/1$ 2$.E/F<.#*2$)"4*0/# *)/ )/.*!
4*0-. ' / /' >
Figure 14: Flipper, Showing Contents of
paragraphs
Table
SQLITE CLIENTS
178
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*0)1$ 24*0-/' .# ( $/# -$)/# !*-(*!1$/# E' )!*F/*-
./' $)/# E/-0/0- F/?)/# EF/' /.4*0 3 0/ -$/--4
.// ( )/.<.#*2$)"4*0,0 -4- .0'/. '*2/# / 3/- >
Figure 15: Flipper, Showing Results of SQL Query
SQLITE CLIENTS
179
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room Security
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
SQLCipher for Android
**(<4 !0'/<2*-&.2$/#/#  1$ D../*&*+4*!$/ ?#$.$.8) <.!-.
$/"* .?*2 1 -<!-*(. 0-$/4./)+*$)/<$/ ./*- .$/./0) )-4+/ ?
*( ++..#*0' *).$ -$)" )-4+/$)"/# $-/E/- ./F<2# )$/$../*- $)
/. </*+-*/ //# $-0. -.?
*-/0)/ '4<.)*/ $) ) -'$ -#+/ -<**(.0++*-/.+'0""' $/
$(+' ( )//$*)<).*2 )+'0"$)$/  $/$*)/#/.0++*-/. )-4+/$*)<
.0#.$+# -!*-)-*$?#$.#+/ -2$''*0/'$) #*2/**/#$.?
Introducing SQLCipher for Android
$) $/ $.+0'$*($)<$/$. .4!*-+ *+' /*"-/# .*0- * )#&
*)$/?$/ '.**7 -.) 3/ ).$*).4./ (<(&$)"$/- '/$1 '4 .4!*- 1 '*+ -.
/*!0)/$*)'$/42$/#($)$(')0( -*!#)" ./*$/ D.*- * ?.
- .0'/<! 2 )-4+/$*)*+/$*).!*-$/ #1  )+0'$.# ?
) *!/# . $. $+# -<2#*.  1 '*+( )/$.*1 -. )4  / /$?#$.*7 -.
/-).+- )/AQTU )-4+/$*)*! 1 -4/#$)"$)/# /. >/<.# (< /?
$/#/# # '+*!/# 0-$)-*% /< /$/ - ' .  $+# -!*-)-*$?#$.
*($) .+- A*(+$' 1 -.$*)*!$/ 2$/#1'.. ./#/($($)*'
$/$*)*!)-*$D.)/$1 $/ '.. .J ?"?< SQLiteOpenHelperK?$+# -!*-
)-*$$.*+ ).*0- <)$!4*0)'$1 2$/#/# $)- . $)++.$5 0 /*/#
)/$1 $)-$ .<$/$.) 7 /$1 .*'0/$*)?
)<$)QOPX< / /$./-/ *7 -$)".0++*-/!*-/# SupportSQLite* ./#/''*2
$+# -!*-)-*$/* +'0"" $)/***(?
183
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
But First, A To-Do Reminder
&$)/# #+/ -*) - /$1 /#- $)".*'0/$*).<2 '**& /.*( * !*-
/-&$)"/*A*$/ (.?#$.* 2. -$1 !-*(/# .(+' ++0$'/0+$)
Exploring Android?#/#+/ - 3+'*- 1-$/$*).*!/#$.* /#/0.  LiveData<
31<)*-*0/$) .?
# $+# -(/ -$'$)/#$.**&*)/$)0 .$/.-$7*)/#/ 3(+' <.*' /D.
,0$&'4- 1$ 2/# *- **( ' ( )/.*!/# /*A** <.+ $8''4/# *-*0/$) .
$/$*)?
The Entity, the Model, and the Store
0-*) **( )/$/4$. ToDoEntity $(+' ( )/ . data class>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport androidx.room.*androidx.room.*
importimport kotlinx.coroutines.flow.Flowkotlinx.coroutines.flow.Flow
importimport org.threeten.bp.Instantorg.threeten.bp.Instant
importimport java.util.*java.util.*
@Entity(tableName = "todos", indices = [IndexIndex(value = ["id"])])
data classdata class ToDoEntityToDoEntity(
valval description: StringString,
@PrimaryKey
valval id: StringString = UUIDUUID.randomUUID().toString(),
valval notes: StringString = "",
valval createdOn: InstantInstant = InstantInstant.now(),
valval isCompleted: BooleanBoolean = falsefalse
) {
constructorconstructor(model: ToDoModelToDoModel) : thisthis(
id = model.id,
description = model.description,
isCompleted = model.isCompleted,
notes = model.notes,
createdOn = model.createdOn
)
funfun toModel(): ToDoModelToDoModel {
returnreturn ToDoModelToDoModel(
id = id,
description = description,
isCompleted = isCompleted,
SQLCIPHER FOR ANDROID
184
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
notes = notes,
createdOn = createdOn
)
}
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM todos")
funfun all(): FlowFlow<ListList<ToDoEntityToDoEntity>>
@Query("SELECT * FROM todos WHERE id = :modelId")
funfun find(modelId: StringString): FlowFlow<ToDoEntityToDoEntity?>
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
suspendsuspend funfun save(varargvararg entities: ToDoEntityToDoEntity)
@Delete
suspendsuspend funfun delete(varargvararg entities: ToDoEntityToDoEntity)
}
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G**)/$/4?&/K
*.$(0'/ (*- *(+' 3. )-$*<) )/$/4&)*2.#*2/**)1 -/$/. '!/*)
!-*( ToDoModel>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport org.threeten.bp.Instantorg.threeten.bp.Instant
importimport java.util.*java.util.*
data classdata class ToDoModelToDoModel(
valval description: StringString,
valval id: StringString = UUIDUUID.randomUUID().toString(),
valval isCompleted: BooleanBoolean = falsefalse,
valval notes: StringString = "",
valval createdOn: InstantInstant = InstantInstant.now()
) {
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G*** '?&/K
)/# *-4</#/(* '($"#/#1 .$")$8)/'4$7 - )/- +- . )//$*)/#)* .
/#  )/$/4<!-*(//4+ *)1 -.$*)./*#1$)"$- /- ! - ) ./**/# -(* '.
 -$1 !-*(*/# - )/$/$ .?
ToDoEntity *)/$).) ./  Store @Dao $)/ -! 2$/#!0)/$*).!*-2*-&$)"2$/#
SQLCIPHER FOR ANDROID
185
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
)/$/$ .?2*Jall() ) find()K- ,0 -$ .)- /0-) Flow *% /.<2#$' /#
save() ) delete() !0)/$*).- (-& 2$/# suspend? ) <*0-0. .
*-*0/$) .!*-- )2-$/ *+ -/$*).?
The Database and the Transmogrifier
0- @Database '..$. ToDoDatabase>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
privateprivate constconst valval DB_NAME = "stuff.db"
@Database(entities = [ToDoEntityToDoEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass ToDoDatabaseToDoDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun todoStore(): ToDoEntityToDoEntity.StoreStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, ToDoDatabaseToDoDatabase::classclass.java, DB_NAMEDB_NAME).build()
funfun newTestInstance(context: ContextContext) =
RoomRoom.inMemoryDatabaseBuilder(context, ToDoDatabaseToDoDatabase::classclass.java)
.build()
}
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G**/. ?&/K
//$ .$) TypeTransmogrifier '../#/*7 -./4+ *)1 -/ -. /2 ) Instant
/$( ./(+.) Long *% /.!*-./*-" $)**(>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport androidx.room.TypeConverterandroidx.room.TypeConverter
importimport org.threeten.bp.Instantorg.threeten.bp.Instant
importimport java.util.*java.util.*
classclass TypeTransmogrifierTypeTransmogrifier {
@TypeConverter
SQLCIPHER FOR ANDROID
186
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
funfun instantToLong(timestamp: InstantInstant?) = timestamp?.toEpochMilli()
@TypeConverter
funfun longToInstant(timestamp: LongLong?) =
timestamp?.let { InstantInstant.ofEpochMilli(it) }
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G4+ -).(*"-$8 -?&/K
ToDoDatabase *7 -. newInstance()A./4' !/*-4!0)/$*).*/#!*-)*-('0. )
!*-/ ./.<2$/#/# '// - $)"& +0- '44( (*-4$)./ *!./*-$)"/*)
$.&?
The Repository
ToDoRepository #$ .''*!/#*.  /$'.< 3+*.$)"$/.*2)*-*0/$) A. 
/#/<$)+-/$0'-<0. .0./*( CoroutineScope /* ).0- /#/2-$/ *+ -/$*).-
)*/) '  -'40 /*0. -)1$"/$*))/# - .0'/$)"' -$)"*!1$ 2(* '.>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport kotlinx.coroutines.CoroutineScopekotlinx.coroutines.CoroutineScope
importimport kotlinx.coroutines.flow.Flowkotlinx.coroutines.flow.Flow
importimport kotlinx.coroutines.flow.mapkotlinx.coroutines.flow.map
importimport kotlinx.coroutines.withContextkotlinx.coroutines.withContext
classclass ToDoRepositoryToDoRepository(
privateprivate valval store: ToDoEntityToDoEntity.StoreStore,
privateprivate valval appScope: CoroutineScopeCoroutineScope
) {
funfun items(): FlowFlow<ListList<ToDoModelToDoModel>> =
store.all().map { all -> all.map { it.toModel() } }
funfun find(id: StringString): FlowFlow<ToDoModelToDoModel?> = store.find(id).map { it?.toModel() }
suspendsuspend funfun save(model: ToDoModelToDoModel) {
withContext(appScope.coroutineContext) {
store.save(ToDoEntityToDoEntity(model))
}
}
suspendsuspend funfun delete(model: ToDoModelToDoModel) {
withContext(appScope.coroutineContext) {
store.delete(ToDoEntityToDoEntity(model))
SQLCIPHER FOR ANDROID
187
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
}
}
}
J!-*( *-*0/$) .G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
The Basics of SQLCipher for Android
# ToDoCrypt (*0' *! /# **&D.+-$(-4.(+' +-*% / *)/$).$/.*2)
$/$*)*!/#*. '.. .<+'0./# 2#*' /*A*++/#/ (+'*4./# (?)$/$*)<
/#$.++.$+# -!*-)-*$<$). /# 0. -- ''42)/./*+-*/ //#*.
/*A*$/ (.?
Adding the Dependency
 / /$($)/$)../)-)-*$-/$!/<1$'' $)1 ) )/-')
$/.($--*-.J ?"?<$)/-4D. )/ -K<0.$)" net.zetetic:android-database-
sqlcipher ./# . 1 )**-$)/ .?*< ToDoCrypt ./#/'$--4/*/#
-*./ -*!'$--$ ./#/$/+0''.$)1$/# dependencies '*.0- $)/# (*0' D.
build.gradle 8' >
implementation "net.zetetic:android-database-sqlcipher:4.4.2"
J!-*( **-4+/G0$'?"-' K
Creating and Applying the Factory
#/"$1 .0. ../* SupportFactory '..?#$.$.)$(+' ( )//$*)*!
SupportSQLiteHelper.Factory ). -1 ../# E"'0 F /2 )$+# -!*-
)-*$)'$ )/.'$& **(?
# .$(+' ./ SupportFactory *)./-0/*-/& . byte[] /#/- +- . )/./#
+..+#-. !*-/# /. ?#$.2$'' 0. $)/2*. .>
I !/# /. * .)*/4 / 3$./<$+# -!*-)-*$2$''- / *) <
)/#$.+..+#-. 2$'' 0. !*- )-4+/$)"/# /.
I !/# /. * . 3$./<$+# -!*-)-*$2$''/-4/**+ )$/0.$)"
/#$.+..+#-. /* -4+//# /.
*24*0" //#/ byte[] !*-/# +..+#-. $.0+/*4*0?)/#$..(+' <2 /& 
1 -4 .4)1 -4'*0.4++-*#>#-*$)"$/?*<2 #1  PASSPHRASE *)./)/
)0. /#/$)/# SupportFactory *)./-0/*->
SQLCIPHER FOR ANDROID
188
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
importimport net.sqlcipher.database.SupportFactorynet.sqlcipher.database.SupportFactory
privateprivate constconst valval DB_NAME = "stuff.db"
privateprivate constconst valval PASSPHRASE = "sekr1t"
@Database(entities = [ToDoEntityToDoEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass ToDoDatabaseToDoDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun todoStore(): ToDoEntityToDoEntity.StoreStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, ToDoDatabaseToDoDatabase::classclass.java, DB_NAMEDB_NAME)
.openHelperFactory(SupportFactorySupportFactory(PASSPHRASEPASSPHRASE.toByteArray()))
.build()
}
}
J!-*( **-4+/G.-G($)G%1G*(G*((*).2- G/**G- +*G**/. ?&/K
+../#/ SupportFactory /* openHelperFactory() *)*0-
RoomDatabase.Builder<)!-*(/# - <**(2$''/& *1 -)$)/ "-/ 2$/#
$+# -!*-)-*$?
Using the Database
#  0/4*!/# SupportSQLite* !($'4*!.$./#/<!*-/# (*./+-/<**(
'$ )/.) $/# -&)*2)*-- *0//# /0'$/ $(+' ( )//$*)? ToDoEntity
) ToDoEntity.Store *)*/) )4/#$)".+ $'!*-$+# -!*-)-*$?
1 ) ToDoDatabase #.%0.//# #)" /*/#/*) openHelperFactory() ''H
)*/#$)" '. $.7 / ? ToDoRepository )$/.'$ )/.J ?"?<1$ 2(* '.K- '.*
0)7 / ?*< 1 -4/#$)"/#/#. )*1 - /*/ $)/# **&%0./2*-&.<2$/#
/#  $(+-*1 ( )/*! )-4+/$*)?
Using the Database… Outside the App
*2*-&2$/#/.  )-4+/ 4$+# -!*-)-*$<4*02$'')  '$ )/
/#/#.$+# -*(+$' $)?$+# -/. .- +*-/' -*..
SQLCIPHER FOR ANDROID
189
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
+'/!*-(.<%0./.$/ /. .- <0/+'$)$/ '$ )/.2$'')*/&)*2#*2
/* '2$/#$+# -D. )-4+/$*).# ( ?*<!*- 3(+' <) $/# -)-*$
/0$*D./. ).+ /*-)*-/# sqlite3 $)-4/#/$.+-/*!)-*$$/. '!2$''
 ' /*2*-&2$/#$+# -!*-)-*$/. .?
-*2. -!*-$/ <#*2 1 -<* ..0++*-/$+# -?
The Costs of SQLCipher for Android
 $)"' /*" /#$"#A"-  )-4+/$*)!*-*)  + ) )4)J. ($)"'4K*)
'$) *!* $)/# ++. (.2*) -!0'?)<$)/-0/#<$). )-$*./#/) 
)-4+/$*)<$/ is 2*) -!0'?/$. 1 ) */#E!- .$) -F)E!- .$).+ #F?
*2 1 -</# - - *./.@%0./)*/$)/ -(.*!(*) 4*--$"#/.?
APK Size
#  0"0$'*! ToDoCrypt $.PO?!/#/<) -'4U*( .!-*(
$)-$ .>
Figure 16: APK Analyzer, Analyzing
ToDoCrypt
Debug Build
SQLCIPHER FOR ANDROID
190
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
$+# -!*-)-*$$)'0 .!0''*+4*!$/ J2$/#/# $+# -
3/ ).$*).$)./'' K<!*-!*0--#$/ /0- .?.$)".+'$/.*-++0)' .<
/# *./!*-$)$1$0'0. -.) '*/' ..<-*++$)"/#/U/*PAQ?*-.*(
++.</#$.2$'')*/ $"+-*' (=!*-*/# -++.J)*/# -0. -. .K<$)"
/#/&$)*!.$5 /*/# *0'  'A- & -?
Runtime Performance
*).$ -$)"/#/ 1 -4/#$)"$. $)" )-4+/ ) -4+/ </# + -!*-() *!
$+# -!*-)-*$$.!$-'4- .*)' ?
# $"" ./ 3+ ). $.2# )4*0*+ )/# /. ?#/0.0''4$.2# )/#*.
'$--$ .2$'' '* ?'.*<$+# -0. .E& 4./- /#$)"FJ/#*0.).*!
-*0).*!QK/**)1 -/4*0-.0++'$ +..+#-. $)/*/# /0' )-4+/$*)
& 4<)/#$./& .$/*!/$( ?*<$!4*0-++'*./*0/*!/# /. 2# )
/# ++./-/.<. ToDoCrypt * .</#$.(& /& '*)" -/#)4*0- 0. /*?#$.
.+ $8 $/$*)*! ToDoCrypt * . not #1 '*$)".// <) + )$)"*)4*0-
/ ./ 1$ <4*02$''. 2#44*0) *) H/# -$ :4.#*2.) (+/4.//
 !*- )4/*A*$/ (." /'* ?*2 1 -<.$) **()(* -))-*$
-#$/ /0- / )/*./ - 1 '*+ -./*2-.*+ )$)"/# /. %0./*) + -
+-* ..</#$.*./$.*)'4$)0-- *) + -+-* ..?
!/ -/#/<$)$1$0'/. *+ -/$*).- (*-  3+ ).$1 <0/0.0''4)*/
-(/$''4(*-  3+ ).$1 ?# *./*!/#  )-4+/$*)) -4+/$*)2$''
-*0"#'4+-*+*-/$*)'/*/# (*0)/*!//#/) ./*  )-4+/ *- -4+/ ?
.- .0'/>
I (''/. *+ -/$*).2$'')*/ + - +/$1 '4.'*2 -2$/#$+# -
!*-)-*$/#)2$/#+'$)$/
I -" /. *+ -/$*).H*) ./#/2 - .*( 2#/+$)!0''- 4H
(4 .0./)/$''4(*- +$)!0'
#$.%0./( )./#/4*02$'') /*.+ ).*(  3/-/$( $)*+/$($5$)"4*0-
/.  ..<.0#.$)"$)$ ./*1*$$+# -!*-)-*$#1$)"/*
 -4+/) )/$- /' /*2'&/#-*0"#''-*2.$)E/' .)FA./4' ,0 -4?
Complexity
# * #)" .$)/# #+/ -2 - /-$1$'?#/$. 0. *0-++-*#/*2-.
()"$)"/# +..+#-. 2./-$1$'?)!*-/0)/ '4</#/'.*( )./#//#
. 0-$/4 ) 8/$./-$1$'<.)//& -2*0')*/#1 $90'//$( 8)$)"/#/
SQLCIPHER FOR ANDROID
191
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#-A* +..+#-. ?
)/#  )</# *(+' 3$/4*!$+# -!*-)-*$*( .)*/!-*(/# '$--4<0/
-/# -!-*(+..+#-. ()" ( )/? 2$'' 3+'*- /#/(*- $) /# ) 3/#+/ -?
SQLCIPHER FOR ANDROID
192
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
SQLCipher and Passphrases
# !0)2$/#$+# -!*-)-*$H+-/$0'-'42# )0. 2$/#**()
 + ) )4$)1 -.$*)!-( 2*-&.H$.$)" //$)"/# +..+#-. /*0. !*-/#
/. ?
) /# +-  $)"#+/ -<2 #-A* /# +..+#-. ?#$.$..$(+' 0/$). 0- ?
1 -40. -#./# .( +..+#-. <)/#/+..+#-. $.!$-'4 .4/*8)*0/1$
- 1 -. A )"$) -$)"/# ++D.?//#/+*$)/<$+# -!*-)-*$.)*
- '. 0-$/4*1 -- "0'-$/ <.*4*0#1 ''/# *./.J ?"?<.$5 <-0)/$(
+ -!*-() K2$/#)* ) 8/.?
)./ <2 ) /*#1 +..+#-. /#/$.0)$,0 /*/# ++$)./''/$*)? ''4<
//& -.2*0'#1 )*24/*8)*0//# +..+#-. !*-)40. -?0/< 1 )$!/# 4
*0'" //# +..+#-. !*-*) 0. -</#/2*0'*)'47 //#/0. -H$/* .)*/
$(( $/ '4*(+-*($. ''*/# -0. -.?
*<$)/#$.#+/ -<2 2$'' 3+'*- '/ -)/$1 24.*!. //$)"0++..+#-. .?
Generating a Passphrase
# '..$.*'0/$*)!*-/#$.+-*' ($./*#1 /# 0. -+-*1$ /# $-*2)
+..+#-. ? 2$'' 3+'*- /#/*+/$*) '/ -$)/# #+/ -?
*2 1 -</4+$''4</#/.*'0/$*)#.$..0 .>
I . -.- !-/**'$& '4/*#**. +**-+..+#-. .< .0#. 12345
I . -.#1 /* )/ -/#/+..+#-.  1 -4/$( <2#$#(& .0.$)" // -
+..+#-. .(*- ))*4$)"
193
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# )$ /#$)"*0//# #-A* +..+#-. $./#//# 0. -* .)*/#1 /*
2*--4*0/$/?# 4%0./0. /# ++)*-(''4?
*0'" ) -/ + -A$)./''/$*)+..+#-. )0. /#//* )-4+//#
/. ? /# )2$)0+2$/#E#$& )) ""F+-*' (>2# - *2 ./*-
/#/" ) -/ +..+#-. <.0#/#/$/))*/  .. 4//& -.C *0'
./*- $/$))*-$)-48' <0//# ))4//& -/#/)" //*/# /. 8' )
'.*" //*/# +..+#-. 8' <)*0-. 0-$/4$.'*2)?
*0'./*- /# " ) -/ +..+#-. $)) )-4+/ 8' ?#$."$1 .0.)*/# -
!*-(*!/# E#$& )) ""F+-*' (>#*2- 2 "*$)"/* )-4+/$/C!/ -''<
(*./ )-4+/$*).4./ (.<'$& $+# -<- ,0$- +..+#-. <).*$! )-4+/$)"
+..+#-. - ,0$- . another +..+#-. <2 . (/*#1 "*// ))*2# - ?
*2 1 -<!*-+'$)8' .<**"' #. // -*+/$*).!*- )-4+/$*)?)+-/$0'-<2
)0. /#  0-$/4'$--4!-*(/#  /+&?#$. )-4+/./0.$)" )-4+/$*)
& 4./#/J*)(*./#-2- K$../*- $)#-2- A& E& 4./*- F<*) /#/$.
 .$") /* /(+ -A- .$./)/?*<2 2$)0+2$/#>
I /.  )-4+/ 4" ) -/ +..+#-.
I #/" ) -/ +..+#-.  )-4+/ 0.$)"& 4/#/$.$) ..$'  3 +/
!-*(*0-++
./$''#1 -$.&*!)//& - ..$)"*0-" ) -/ +..+#-. <.2 2$''.
'/ -$)/# #+/ -<0/$/- ''4./-/./*-(+0+/# $90'/4<)2$/#.*(
- !0'2*-&<2 )- 0 /# -$.& 1 )!0-/# -?
*<2$/#''*!/#/$)($)<' /0.'**&/ /# ToDoGen (*0' *! /# **&D.+-$(-4
.(+' +-*% /?#$.$.'*) *! ToDoCrypt /#/2 .2$)/# +- 1$*0.#+/ -<
3 +//#/2 0. /#  0-$/4'$--4)" ) -/ +..+#-. <-/# -/#)
#-A* *) ?
Creating the Passphrase
# +..+#-. /#/2 " ) -/ 2$'') 1 -  )/ - 40. -H$/$.+0- '4!*-
$)/ -)'0. ? ) <2 *)*/) /*2*--4*0/#*2 .4$/$./*/4+ $)?
*2 1 -<  / /$- ,0$- .$)-4& 4./*)*/#1 5 -*4/ 1'0 .?
*<!*-/# +0-+*. .*!/#$..(+' <2 2$''"*2$/#RQA4/ +..+#-. <- % /$)"
)4/#/*)/$)5 -*.1'0 >
SQLCIPHER AND PASSPHRASES
194
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
privateprivate funfun generatePassphrase(): ByteArrayByteArray {
valval random = SecureRandomSecureRandom.getInstanceStrong()
valval result = ByteArrayByteArray(PASSPHRASE_LENGTHPASSPHRASE_LENGTH)
random.nextBytes(result)
// filter out zero byte values, as SQLCipher does not like them
whilewhile (result.contains(0)) {
random.nextBytes(result)
}
returnreturn result
}
J!-*( ** )G.-G($)G%1G*(G*((*).2- G/**G- +*G..+#-.  +*.$/*-4?&/K
Safely Storing the Passphrase
#/+..+#-. $." ) -/ 4 PassphraseRepository<& 4
EncryptedFile $)./) ?!2 *)*/#1 +..+#-. 8' <2 " ) -/ 
+..+#-. ).1 $/$)) )-4+/ !*-(?!2 *#1 +..+#-. 8' <2
 -4+/$//*" //# +..+#-. /*0. >
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport android.content.Contextandroid.content.Context
importimport androidx.security.crypto.EncryptedFileandroidx.security.crypto.EncryptedFile
importimport androidx.security.crypto.MasterKeysandroidx.security.crypto.MasterKeys
importimport java.io.Filejava.io.File
importimport java.security.SecureRandomjava.security.SecureRandom
privateprivate constconst valval PASSPHRASE_LENGTH = 32
classclass PassphraseRepositoryPassphraseRepository(privateprivate valval context: ContextContext) {
funfun getPassphrase(): ByteArrayByteArray {
valval file = FileFile(context.filesDir, "passphrase.bin")
valval encryptedFile = EncryptedFileEncryptedFile.BuilderBuilder(
file,
context,
MasterKeysMasterKeys.getOrCreate(MasterKeysMasterKeys.AES256_GCM_SPECAES256_GCM_SPEC),
EncryptedFileEncryptedFile.FileEncryptionSchemeFileEncryptionScheme.AES256_GCM_HKDF_4KBAES256_GCM_HKDF_4KB
).build()
returnreturn ifif (file.exists()) {
encryptedFile.openFileInput().use { it.readBytes() }
} elseelse {
generatePassphrase().also { passphrase ->
SQLCIPHER AND PASSPHRASES
195
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
encryptedFile.openFileOutput().use { it.write(passphrase) }
}
}
}
privateprivate funfun generatePassphrase(): ByteArrayByteArray {
valval random = SecureRandomSecureRandom.getInstanceStrong()
valval result = ByteArrayByteArray(PASSPHRASE_LENGTHPASSPHRASE_LENGTH)
random.nextBytes(result)
// filter out zero byte values, as SQLCipher does not like them
whilewhile (result.contains(0)) {
random.nextBytes(result)
}
returnreturn result
}
}
J!-*( ** )G.-G($)G%1G*(G*((*).2- G/**G- +*G..+#-.  +*.$/*-4?&/K
# .+ $8- $+ 0. # - <$)/ -(.*!/# MasterKeys )1-$*0. Scheme
*% /.<*( .!-*(**"' )++ -./* - .*)' . /*! !0'/.?
#/ EncryptedFile '..*( .!-*(/# androidx.security:security-crypto
 /*/# (*0' D. build.gradle 8' >
implementation "androidx.security:security-crypto:1.0.0"
J!-*( ** )G0$'?"-' K
# PassphraseRepository $/. '!$.- / 4*$)$) ToDoApp<.+-/*!(*0' >
single { PassphraseRepositoryPassphraseRepository(androidContext()) }
J!-*( ** )G.-G($)G%1G*(G*((*).2- G/**G**++?&/K
# ) / 7 /$./#/*0- PrefsRepository ./*- ./#/" ) -/ +..+#-. $)/#
)-4+/  SharedPreferences<)/#/- +*.$/*-4$.1$'' !*-*/# -*% /./*
0. 1$*$)?
Using the Generated Passphrase
0- ToDoDatabase !/*-4!0)/$*)) ./#/" ) -/ +..+#-. ?*<2 $//*
/# !0)/$*).$")/0- )- (*1 /# #-A*  PASSPHRASE /#/ ToDoCrypt 0. >
SQLCIPHER AND PASSPHRASES
196
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
importimport net.sqlcipher.database.SupportFactorynet.sqlcipher.database.SupportFactory
privateprivate constconst valval DB_NAME = "stuff.db"
@Database(entities = [ToDoEntityToDoEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass ToDoDatabaseToDoDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun todoStore(): ToDoEntityToDoEntity.StoreStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext, passphrase: ByteArrayByteArray) =
RoomRoom.databaseBuilder(context, ToDoDatabaseToDoDatabase::classclass.java, DB_NAMEDB_NAME)
.openHelperFactory(SupportFactorySupportFactory(passphrase))
.build()
}
}
J!-*( ** )G.-G($)G%1G*(G*((*).2- G/**G- +*G**/. ?&/K
# )*$)- / .*0- ToDoDatabase<2 +0''/# +..+#-. !-*(
PassphraseRepository<0.$)"$//* " ) -/ $!)  >
single {
valval passRepo: PassphraseRepositoryPassphraseRepository = getget()
ToDoDatabaseToDoDatabase.newInstance(androidContext(), passRepo.getPassphrase())
}
J!-*( ** )G.-G($)G%1G*(G*((*).2- G/**G**++?&/K
# - .0'/$.)*#)" !-*(/# 0. -D../)+*$)/<0/2 #1 - +' /# #-A
* +..+#-. 2$/#" ) -/ +..+#-. <& 4 /+&A()"  1$
)-4+/$*)?
Pros and Cons
#$.- ,0$- - '/$1 '4'$//' $)/# 24*!* #)" .?/* .)*/#)" /# 0. -
3+ -$ ) ?)<$/$.'*/ // -!-*(. 0-$/4./)+*$)//#)#1$)"#-A
* +..+#-. ?
SQLCIPHER AND PASSPHRASES
197
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*2 1 -<$/$.)*/+ -! />
I EncryptedFile (4#1 +-*' (.*).*( #-2- ?
I # /. $.0)0.' < 3 +/1$/# ++?*- 3(+' <4*0))*/*+4
/# /. *7/#  1$ )0. $/2$/#)*/# -'$ )/<0)' ..4*0'.*
--)" /*" /*+4*!/# +..+#-. ?) 1 '*+( )/</#/($"#/ 
(// -*!'*""$)"$/2$/#*"/@.*'*)".4*0*)*/$ )/''4.#$+
.0#* ?)+-*0/$*)</#$.2$''0. $..0 .2$/#&0+.*'0/$*).<$!/# 4
&0+/# /. 0/)*//#  )-4+/$*)& 4)  /*/0''40. /#/
/. ?
I )4*) 2#*)" /$)/*/# +#*) )" /$)/*/# /. 1$/# ++?
E*$' )"$) -$)"F//&.)/-$&0. -.$)/*#)$)"*1 -/# $-+#*) .
$))0)'*& .// ?#$.) $(+-*1 .*( 2#/4- /$)"0./*(
MasterKeys /#/ $)'0 .*+/$*).'$&
setUserAuthenticationRequired(true)?
I # $(+' ( )//$*)# - & +./#$)"..$(+' )* ./# $.&G!*-/#
EncryptedFile *)/# 0-- )//#- <2#$#(42 '' /# ($)
++'$/$*)/#- ?#$.$.)*/$ '<)+-*0/$*)A"- ++$ ''4
2*0'* // -%*?
Collecting a Passphrase
)*/# -*+/$*)$./*#1 /# +..+#-.  ./*- $)/# 0. -D.( (*-4? #1
/# (#**. +..+#-. 2# )2 "*/*- / /# /. <)2 #1 /# (
.0++'4/#/+..+#-. "$)'/ -2# )*+ )$)"/# /. $)!- .#+++-* ..?
Adding a Passphrase Field
) *2).$ /*/#$.++-*#$./#/2 #1 /*.*( /**0-++/**'' /
/#/+..+#-. !-*(/# 0. -?
*& +/#  3(+' .$(+' H$!+ -#+.)*/1 -4+- //4H/#$. 3(+' %0./.#*1 .
+..2*- EditText ) Button $)/*/# '4*0//#/2 0. !*-/# '$./*!/*A*
$/ (.?
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
SQLCIPHER AND PASSPHRASES
198
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
android:layout_height="match_parent"
tools:context=".ui.MainActivity">>
<TextView<TextView
android:id="@+id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_empty"
android:textAppearance="?android:attr/textAppearanceMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />/>
<androidx.recyclerview.widget.RecyclerView<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />/>
<EditText<EditText
android:id="@+id/passphrase"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/passphrase_hint"
android:importantForAutofill="no"
android:inputType="textPassword"
android:maxLines="1"
app:layout_constraintBottom_toTopOf="@id/open"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />/>
<Button<Button
android:id="@+id/open"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/button_open"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/passphrase" />/>
<androidx.constraintlayout.widget.Group<androidx.constraintlayout.widget.Group
SQLCIPHER AND PASSPHRASES
199
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
android:id="@+id/authGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="passphrase,open"
tools:visibility="visible" />/>
</androidx.constraintlayout.widget.ConstraintLayout></androidx.constraintlayout.widget.ConstraintLayout>
J!-*( **. -G.-G($)G- .G'4*0/G/**M-*./ -?3('K
# *% /$1 $./*/# ).#*2/#*. 2$" /.//# *0/. /<.*2 )*'' //#
+..+#-. !-*(/# 0. ->
Figure 17: ToDoUser, Requesting a Passphrase
Detecting We Need a Passphrase
0-1$ 2(* '2$'') /*/-&2# /# -*-)*/2 ) /* .&$)"!*-/#
+..+#-. ).#*0' .#*2$)"/#*. ) 22$" /.?*<2 #1 *0-1$ 2A.// H
# - ''  RosterViewState H2-+*/#/# '$./*!/*A*$/ (.):"
$)$/$)"2# /# -*-)*/0/# )/$/$*)$.- ,0$- >
SQLCIPHER AND PASSPHRASES
200
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
data classdata class RosterViewStateRosterViewState(
valval items: ListList<ToDoModelToDoModel> = listOf(),
valval authRequired: BooleanBoolean = falsefalse
)
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G0$G-*./ -G*./ -*/*-?&/K
/# )#1 *0- LiveData !-*(/#$.1$ 2(* ' !*-/#$. RosterViewState?)<
2 )"*# )$)$/$'$5 $//*./-/2$/# authRequired = true<.$) /# :*2
*!/#$.+++- //4(0#"0-)/ ./#/$!/#$.1$ 2(* '$.8-./ $)"- / <2
- "*$)"/*) /# +..+#-. >
privateprivate valval _states =
MutableLiveDataMutableLiveData<RosterViewStateRosterViewState>(RosterViewStateRosterViewState(authRequired = truetrue))
valval states: LiveDataLiveData<RosterViewStateRosterViewState> = _states
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G0$G-*./ -G*./ -*/*-?&/K
0-1$ 2H RosterListFragment H)/# )*. -1 /#/./- (*!1$ 2A.// .)
0+/ /# 1$.$' . /*!2$" /./*(/#>
motor.states.observe(viewLifecycleOwner) { state ->
adapter.submitList(state.items)
whenwhen {
state.authRequired -> {
binding.authGroup.isVisible = truetrue
binding.empty.isVisible = falsefalse
}
state.items.isEmpty() -> {
binding.authGroup.isVisible = falsefalse
binding.empty.isVisible = truetrue
binding.empty.setText(RR.string.msg_empty)
}
elseelse -> {
binding.authGroup.isVisible = falsefalse
binding.empty.isVisible = falsefalse
}
}
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G0$G-*./ -G*./ -$./-"( )/?&/K
*<2# )2 '0)#/# ++)2 " //*/#$..- )<$)$/$''4<2 2$''.&!*-/#
+..+#-. ?
SQLCIPHER AND PASSPHRASES
201
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Applying the Passphrase
# )/# 0. -'$&./# 0//*)<2 '') open() !0)/$*)*)/# 1$ 2(* '
JRosterMotorK<.0++'4$)"/# *)/ )/.*!/# EditText>
binding.openopen.setOnClickListener {
motor.openopen(binding.passphrase.text.toString())
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G0$G-*./ -G*./ -$./-"( )/?&/K
$/#/# +- 1$*0.1 -.$*).*!/# . .(+' .< RosterMotor *0'%0././-/2*-&$)"
2$/#/# /. -$"#/24<./# +..+#-. 2. $/# -#-A* *-++A
" ) -/ ?*2<.$) 2 ) 0. -A.0++'$ +..+#-. <2 ) /* '4*+ )$)"
/# /. 0)/$'2 #1 /#/+..+#-. <)/#/$.2#/ open() * .>
funfun open(passphrase: StringString) {
viewModelScope.launch {
ifif (repo.openDatabase(context, passphrase)) {
repo.items().collect { _states.value = RosterViewStateRosterViewState(items = it) }
} elseelse {
_states.value = RosterViewStateRosterViewState(authRequired = truetrue)
}
}
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G0$G-*./ -G*./ -*/*-?&/K
openDatabase() *) ToDoRepository 2$''*+ )*-- / /# /. 0.$)"/#
.0++'$ +..+#-. ?/2$''- /0-) true $!/#/.0 .*- false */# -2$. ?)/#
!*-( -. <2 '**0-/*A*'$./$/ (.) ($/!- .#1$ 2A.// 2$/#/#//
J)2$/# authRequired . //* false</#  !0'/K?)/# '// -. <2  ).0- /#/
*0- LiveData #. authRequired = false?# 2$'' /#/$!/# 0. -($.A/4+ .
/# $-+..+#-. <)*/#$)"- ''4#++ ).H/#$.$.)*/$ '<0/$/& +./#
3(+' .$(+' ?
Creating and Opening the Database
) -'$ - 3(+' .< ToDoRepository -  $1  ToDoEntity.Store .*)./-0/*-
+-( / -1$ + ) )4$)1 -.$*)?*2<$/-  $1 . ToDoDatabase.Factory
$)./ >
SQLCIPHER AND PASSPHRASES
202
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
classclass ToDoRepositoryToDoRepository(
privateprivate valval dbFactory: ToDoDatabaseToDoDatabase.FactoryFactory,
privateprivate valval appScope: CoroutineScopeCoroutineScope
) {
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
#/$. 0. 2#$' +- 1$*0.'4*$)*0'*+ )/# /. *)$/.*2)<2 )*2
) /*2$/0)/$'/# +..+#-. $.1$'' ?
ToDoDatabase.Factory &)*2.#*2/**+ )/. <"$1 )+..+#-. >
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
importimport net.sqlcipher.database.SupportFactorynet.sqlcipher.database.SupportFactory
privateprivate constconst valval DB_NAME = "stuff.db"
@Database(entities = [ToDoEntityToDoEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass ToDoDatabaseToDoDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun todoStore(): ToDoEntityToDoEntity.StoreStore
classclass FactoryFactory {
funfun newInstance(context: ContextContext, passphrase: ByteArrayByteArray) =
RoomRoom.databaseBuilder(context, ToDoDatabaseToDoDatabase::classclass.java, DB_NAMEDB_NAME)
.openHelperFactory(SupportFactorySupportFactory(passphrase))
.build()
}
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G- +*G**/. ?&/K
# koinModule $) KoinApp )*2. /.0+.$)"' /*)$)./) *!
ToDoDatabase.Factory</*./$.!4/# ToDoRepository - ,0$- ( )/>
single { ToDoDatabaseToDoDatabase.FactoryFactory() }
single {
ToDoRepositoryToDoRepository(
getget(),
getget(named("appScope"))
)
SQLCIPHER AND PASSPHRASES
203
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G**++?&/K
ToDoRepository )*2#. db +-*+ -/4/#/#*'./# ToDoDatabase@$!$/#. )
*+ ) ?/# -2$. <$/- ($). null>
privateprivate varvar db: ToDoDatabaseToDoDatabase? = nullnull
funfun isReady() = db != nullnull
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
# openDatabase() !0)/$*)/#/*0-1$ 2(* ''' 2$''0. /#/
ToDoDatabase.Factory /**+ )/# /. )+*+0'/ /#/ db +-*+ -/4>
suspendsuspend funfun openDatabase(context: ContextContext, passphrase: StringString): BooleanBoolean {
trytry {
db = dbFactory.newInstance(context, passphrase.toByteArray())
db?.todoStore()?.count()
} catchcatch (t: ThrowableThrowable) {
trytry { db?.close() } catchcatch (t2: ThrowableThrowable) { }
db = nullnull
LogLog.e("ToDoUser", "Exception opening database", t)
}
returnreturn isReady()
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
openDatabase() 0. ./# ToDoDatabase.Factory /*" //# ToDoDatabase $)./) <
.0++'4$)"/# +..+#-. ?*2 1 -/#/* .)*//-$"" -/# /. /* opened?
*-(''4<#1$)"/# /.  *+ ) '5$'4$.8) **(! /0- ?)/#$.. <
/#*0"#<2 #1 /*2*--4*0//# +*..$$'$/4/#//# +..+#-. $.2-*)"?*<2
)*2#1  count() !0)/$*)*)*0- DAO</#/%0./- /0-).*0)/*!/# /*A*$/ (
-*2.>
@Dao
interfaceinterface StoreStore {
@Query("SELECT * FROM todos ORDER BY description")
funfun all(): FlowFlow<ListList<ToDoEntityToDoEntity>>
@Query("SELECT * FROM todos WHERE id = :modelId")
funfun find(modelId: StringString?): FlowFlow<ToDoEntityToDoEntity?>
@Query("SELECT COUNT(*) FROM todos")
suspendsuspend funfun count(): LongLong
SQLCIPHER AND PASSPHRASES
204
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@Insert(onConflict = OnConflictStrategyOnConflictStrategy.REPLACEREPLACE)
suspendsuspend funfun save(varargvararg entities: ToDoEntityToDoEntity)
@Delete
suspendsuspend funfun delete(varargvararg entities: ToDoEntityToDoEntity)
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G- +*G**)/$/4?&/K
#$.$. .$") /* !$-'4# +/* 3 0/ 2#$'  ).0-$)"/#//# +..+#-.
2*-&.?*< openDatabase() ''. count()?!/#//#-*2.) 3 +/$*)</# )
+- .0('4/# +..+#-. 2.($.A )/ - <.*2 close() /# /. ). / db
&/* null /*$)$/ /#/2 #1 )*/.0 ..!0''4*+ ) /# /. ?
openDatabase() /# )- /0-)./# Boolean $)$/$)".0 ..*-!$'0- ?''*!/#$.$.
*) $) suspend !0)/$*)<.*$/) + -!*-( *)&"-*0)/#- ?
# - ($)$)"!0)/$*).*) ToDoRepository )*20. db /* ../# /. )
/#-*2) 3 +/$*)$!/# /. $.)*/0-- )/'4*+ )>
funfun items(): FlowFlow<ListList<ToDoModelToDoModel>> =
db?.todoStore()?.let { store ->
store.all().map { all -> all.map { it.toModel() } }
} ?: throwthrow IllegalStateExceptionIllegalStateException("database is not open")
funfun find(id: StringString?): FlowFlow<ToDoModelToDoModel?> =
db?.todoStore()?.let { store ->
store.find(id).map { it?.toModel() }
} ?: throwthrow IllegalStateExceptionIllegalStateException("database is not open")
suspendsuspend funfun save(model: ToDoModelToDoModel) {
withContext(appScope.coroutineContext) {
db?.todoStore()?.save(ToDoEntityToDoEntity(model))
?: throwthrow IllegalStateExceptionIllegalStateException("database is not open")
}
}
suspendsuspend funfun delete(model: ToDoModelToDoModel) {
withContext(appScope.coroutineContext) {
db?.todoStore()?.delete(ToDoEntityToDoEntity(model))
?: throwthrow IllegalStateExceptionIllegalStateException("database is not open")
}
}
J!-*( **. -G.-G($)G%1G*(G*((*).2- G/**G- +*G** +*.$/*-4?&/K
SQLCIPHER AND PASSPHRASES
205
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Pros and Cons
# "**) 2.$./#//# 0. -&)*2./# +..+#-. ?&0+.*!/# /. )
 ( )- ./*- <)/# 0. -.#*0'- /$) ../*/# (?# /.
*0' 1 ) /-).! -- /*.*( */# - 1$ *-+'/!*-(<)/# 0. -)- /$)
 ..?
# ) 2.$./#//# 0. -&)*2./# +..+#-. ?#$.( )./#/ /# 0. -($"#/
 *)1$) /*"$1 0+/# +..+#-. )/# - 4'*. *)/-*'*1 -/# $-/?
Multi-Factor Authentication
0'/$A!/*-0/# )/$/$*)$.''*0/*($)$)"(0'/$+' /.*0- ./*1'$/
$ )/$/4?)'..$/2*A!/*-0/# )/$/$*)</# +#-. E.*( /#$)"4*0#1 )
.*( /#$)"4*0&)*2F$.*!/ )0. /* .-$ /# !/*-.?E*( /#$)"4*0#1 F
($"#/ #-2- /*& )<*-* " ) -/ 4)0/# )/$/*-++?
E*( /#$)"4*0&)*2F$.+..+#-. ?
$($'-'4<2 *0'*($) /# /2*+..+#-. / #)$,0 ..#*2)$)/#$.#+/ -<
2# - /# /0'+..+#-. "$1 )/*$+# -!*-)-*$*($) .>
I 0. -A.0++'$ +..+#-. < and
I " ) -/ +..+#-. /#/ 7 /$1 '4$./$ /*/# 0. -D.$'$/4/*
0/# )/$/ "$).//# $- 1$
-<2 *0'*($) 0. -A.0++'$ +..+#-. )) 3/ -)''4A" ) -/ /*& )<
.0#.1$ A+' #-2- /*& ).?
-<2 *0'*($) ''/#- >0. -A.0++'$ +..+#-. <" ) -/ *)A 1$
+..+#-. <)#-2- /*& )?
-<2 *0'*( 0+2$/#4 /*/# -.*0- .*!+..+#-. (/ -$')*7 -/# (
.*+/$*).?
)/#  )<$+# -!*-)-*$* .)*/&)*2*-- #*24*0" //#
+..+#-. *-#*2$/"*/.. (' !-*($)$1$0'+$  .?#/$.0+/*4*0<.4*0
/-4/*./-$& /# ')  /2 ). 0-$/4)0.$'$/4?
SQLCIPHER AND PASSPHRASES
206
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
The Risks of StringString
) :2$) ToDoUser $./#//# +..+#-. $. $)"+.. -*0). String?
#$' String $.*)1 )$ )/< String $.$((0/' ? #1 )*24/*" /-$*!
String<*/# -/#)/*' /"**!$/<#*+ /#/$/" /."-" A*'' / ,0$&'4</# )
#*+ /#/.*( /#$)" '. ''*/ ./#/.( $/*!( (*-4)*1 -2-$/ .$/?
..+#-. .$)( (*-4- '$& )0' -2./ >/# 4. -1 -*' ))*2- %0./
$../ -.2$/$)"/*#++ )?*-/0)/ '4<0.0''4<$../ -.*)*/#++ )<0/!*-
.*( + *+' <E0.0''4F$.$).09$ )/?
*'*)".+..+#-. .- ($)$)( (*-4<$/$.+*..$' </#-*0"#1) 
/ #)$,0 .<!*-/# (/*  3/-/ !-*(( (*-4?#/'(*./'24.- ,0$- .
.*( *4/*#1 +#4.$' ../*/#  1$ < ' /**/$).0+ -0. -
+-$1$' " .J?&??<E-**//#  1$ FK<) ' /*0. .+ $'$5 /**'./*.1 
.)+.#*/*!/# ++D.# +/*$.&?#*. - .$")$8)/--$ -.<0/*) ./#/-
()" ' 4//& -.2#*- .&$'' <2 '/#4<*-*/#?
 ''4<*) 2 0. /# +..+#-. /*"$) ../*/#  )-4+/ /. <2
2*0'' -/# +..+#-. $/. '!*0/*!( (*-4?#/$.)*/+*..$' 2$/# String?
#$.$.2#4 ToDoGen 0. . ByteArray?$+# -!*-)-*$<4 !0'/<2$''E5 -*
*0/F/# ByteArray<- +'$)"''$/.4/ .2$/#5 -*.<*) /# +..+#-. #. )
0. ?#$. ).0- ./#//# +..+#-. ))*'*)" - - /-$ 1 1$ 3($)$)"/#
++'$/$*)D.# +?
SQLCIPHER AND PASSPHRASES
207
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Managing SQLCipher
0./.2 ) /*/#$)&*0/()"$)"*0-*-$)-4$/ /. .<2 ) /*
/#$)&*0/*$)"/# .( !*-$+# -!*-)-*$/. .?)/#$.#+/ -<
2 2$''-$ :4 3+'*- .*( *!/#*. *((*)*) -).?
Backup and Restore
-'$ -$)/# **&<2 '**& /#*2/*&0+)- ./*- $/ /. ?#
.( ( #)$.(.2*0' 0. !*-$(+*-/G 3+*-/*+ -/$*).</*(& *+4*!
/# /. 1$'' !*-0. -./*/& /*)*/# -(#$) <!*- 3(+' ?
$/#) )-4+/ /. </#$)"." /(*- *(+'$/ ?*0"#'4.+ &$)"</# -
- /2*. )-$*./**).$ ->
P? *02)//*&0+/# /. )& +$/ )-4+/ ?#$.2*0' "**
!*-. .2# - /# +..+#-. $.0. -A.0++'$ <*-!*-. .2# - 4*0-
"*$)"/*&0+/# +..+#-. .2 ''J4.*( . 0- ( ).K?$($'-'4<
4*0($"#/ /-4$)"/*- ./*- /# /. /#/$.'- 4 )-4+/ ?
Q? *02)//* 3+*-//# /. $) -4+/ !*-(<*) /#/) 0. 
4*-$)-4$/ '$ )/.<)*/)  ..-$'4*) ./#/.0++*-//# $+# -
!*-(/?$($'-'4<4*0($"#/ /-4$)"/*$(+*-/ -4+/ /. )
./*- /#//$)4*0- )-4+/ /. ?
# 8-./. )-$*2*-&.+- //4(0#/# .( .2$/#- "0'-$/ /. .?!
4*0- *+4$)"0.$)"8' .4./ (.</# /$)/# /. - ($).$)$/.
0-- )/!*-(?)<2$/#$+# -!*-)-*$</# E0-- )/!*-(F$. )-4+/ ?*<
$!4*0*+4/# /. 8' !*-) )-4+/ /. <4*02$)0+2$/#*+4*!
/#/ )-4+/ /. ?
209
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
# $(+*-/G 3+*-//*0) )-4+/ JE+'$)/ 3/FK/. ." /.(*- $)1*'1 <.2
2$''. $) /# ImportExportCrypt (*0' *! /# **&D.+-$(-4.(+' +-*% /?
#/.(+' 0$'.0+*)/# ImportExport .(+' !-*( /# #+/ -*)&$)"0+
/. ?*2 1 -</#$./$( </# /. $. )-4+/ <0.$)"#-A* 
+..+#-. !*-.$(+'$$/4?)<*0-)*2#.81 0//*).<$)'0$)"*+/$*).!*-
*/#+'$)) )-4+/ $(+*-/) 3+*-/>
Figure 18: ImportExportCrypt, Showing Available Buttons
Exporting a Plaintext Database
0-++#.$+# -!*-)-*$/. < )-4+/ 4*0-#-A* 
+..+#-. ? 2)//* 3+*-/+'$)/ 3/*+4*!/#//. >.( .# (<.(
/<%0./2$/#*0//#  )-4+/$*)?
$+# -!*-)-*$#.- $+ !*-*$)"/#$.?$) $/$)1*'1 .!$-$/*!
()0'/. ()$+0'/$*)<-/# -/#)0.$)"**(*-/#
SupportSQLiteDatabase <2 2$''"*E*'.#**'F)2*-&2$/#/# $+# -
!*-)-*$1 -.$*)*! SQLiteDatabase $- /'4?)/#  )<2 2$)0+2$/#
decryptTo() !0)/$*)*) SQLCipherUtils *% //#//& .>
I +/#/**0- )-4+/ /. 8'
MANAGING SQLCIPHER
210
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
I +/#/*/#  ./$)/$*)+'$)/ 3//. 8'
I # +..+#-. !*-*0- )-4+/ /.
I Context< 0. /#$.$.)-*$<)4*0))*/" /*0/*! $)/#
(*-)$)"2$/#*0/ Context
!/ -''$)"/#$.'*&$)"!0)/$*)<*0-+'$)/ 3//. .#*0'- .$ //#
- ,0 ./ +/#?
Examining the Utility Function
# !0)/$*)'**&.$/)./4>
funfun decryptTo(
ctxt: ContextContext,
originalFile: FileFile,
targetFile: FileFile,
passphrase: ByteArrayByteArray?
) {
SQLiteDatabaseSQLiteDatabase.loadLibs(ctxt)
ifif (originalFile.exists()) {
valval originalDb = SQLiteDatabaseSQLiteDatabase.openDatabase(
originalFile.absolutePath,
passphrase,
nullnull,
SQLiteDatabaseSQLiteDatabase.OPEN_READWRITEOPEN_READWRITE,
nullnull,
nullnull
)
SQLiteDatabaseSQLiteDatabase.openOrCreateDatabase(
targetFile.absolutePath,
"",
nullnull
).close()
// create an empty database
//language=text
valval st =
originalDb.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
st.bindString(1, targetFile.absolutePath)
st.execute()
originalDb.rawExecSQL("SELECT sqlcipher_export('plaintext')")
originalDb.rawExecSQL("DETACH DATABASE plaintext")
valval version = originalDb.version
MANAGING SQLCIPHER
211
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
st.close()
originalDb.close()
valval db = SQLiteDatabaseSQLiteDatabase.openOrCreateDatabase(
targetFile.absolutePath,
"",
nullnull
)
db.version = version
db.close()
} elseelse {
throwthrow FileNotFoundExceptionFileNotFoundException(originalFile.absolutePath + " not found")
}
}
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
!4*02$.#/*/& $/*)!$/#/#//# !0)/$*)2*-&.<! '!- /*.&$+# /*/#
) 3/. /$*)?
0/</* 3+'$)2#//#$.!0)/$*)* .<' /D./& $/./ +4./ +?
SQLiteDatabaseSQLiteDatabase.loadLibs(ctxt)
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
$+# -!*-)-*$*)/$).*(+' / *+4*!$/ 2$/#/# $+# -!*-
)-*$ 3/ ).$*).*(+$' $)?#$.$.)/$1 * <*(+$' 1$/# ?# )
2 0. /# $+# -!*-)-*$ + ) )4<2 " //#/*(+$' * $)/#
!*-(*! .so 8' .$)/# ?#$..// ( )/'*./# $+# -!*-)-*$
'$--4?
# )<!/ -# &$)"/*. $!/#  )-4+/ /. 8'  3$./.<2 *+ )$/0.$)"/#
$+# -!*-)-*$ $/$*)*! SQLiteDatabase>
valval originalDb = SQLiteDatabaseSQLiteDatabase.openDatabase(
originalFile.absolutePath,
passphrase,
nullnull,
SQLiteDatabaseSQLiteDatabase.OPEN_READWRITEOPEN_READWRITE,
nullnull,
nullnull
)
MANAGING SQLCIPHER
212
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
# +-$(-4$7 - ) !-*(2#/4*02*0'*2$/#/# !-( 2*-&
SQLiteDatabase $./#/4*0.0++'4/# +..+#-. /*0. /**+ )/# /. ?
/# )) /*. /0+/# /-" /+'$)/ 3//. ?) "$)2 0. /#
$+# -!*-)-*$ $/$*)*! SQLiteDatabase<0//#$./$( 2 0. "" ./#
+..+#-. <2#$#/ ''.$+# -/*' 1 /#$./.  -4+/ >
SQLiteDatabaseSQLiteDatabase.openOrCreateDatabase(
targetFile.absolutePath,
"",
nullnull
).close()
// create an empty database
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
*02$'')*/$ /#/2 /# )$(( $/ '4'*. /#$./. ? ) /# /.
/* 3$./<0/2 *)*/) J*-2)/K$//* *+ )H2 - "*$)"/*0. $/$)
.'$"#/'4$7 - )/!.#$*)@1$ ATTACH DATABASE>
//language=text
valval st =
originalDb.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
st.bindString(1, targetFile.absolutePath)
st.execute()
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
ATTACH DATABASE $. +-/*!$/ D..4)/3?/''*2.4*0/**+ )/2*/. .
/*) ?# /. /#/4*0*+ ) 0.$)" SQLiteDatabase $.$ )/$8 )*-(''4=
/# /. /#/4*0//#$.$ )/$8 4/# )( 4*0"$1 $/1$/# AS & 42*-?
*<$)/#$.. <2 - //#$)"/#  (+/4+'$)/ 3//. . plaintext?
$+# - 3/ ). ATTACH DATABASE /*/&  KEY & 42*-/#/+-*1$ ./#
+..+#. /*0. H$)/#$.. <2 - +..$)"/#  (+/4./-$)"</*$)$/ /#/
/#$./. $.)*/ )-4+/ ?# ? $)/# ATTACH DATABASE .// ( )/$./# !0''4A
,0'$8 +/#/*/# /. 8' <2#$#2 .0++'4.,0 -4+-( / -0.$)"
bindString()?# ) / 7 /*! 3 0/$)"/#$..// ( )/$./#/2 )*2#1
*/#/. .*+ )/*) ?
$+# -*( .2$/# sqlcipher_export() !0)/$*)/#/2 )$)1*& /#/
*+$ ./# *)/ )/.*!/# /. !-*(/# *-$"$)'/*/# //# *) >
MANAGING SQLCIPHER
213
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
originalDb.rawExecSQL("SELECT sqlcipher_export('plaintext')")
originalDb.rawExecSQL("DETACH DATABASE plaintext")
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
#  7 /*!/# . /2*.// ( )/.$./**+4'(*./''*!/#  )-4+/ /!-*(
/# *-$"$)'/. /*/# +'$)/ 3//. </# )/* /#/#/+'$)/ 3/
/. J/# - 4'*.$)"$/K?
) +$  *!//#/ sqlcipher_export() ($.. .$./# .# (1 -.$*)?*<*0-
8)'./ +$./*" //# .# (1 -.$*)!-*(/#  )-4+/ /. )++'4$//*
/# +'$)/ 3//. >
valval version = originalDb.version
st.close()
originalDb.close()
valval db = SQLiteDatabaseSQLiteDatabase.openOrCreateDatabase(
targetFile.absolutePath,
"",
nullnull
)
db.version = version
db.close()
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
# - .0'/$./#/''*!*0-/- .$ .$)*0-) 2+'$)/ 3//. <)*/#
/. 8' .- '*. 2# )2 - *) ?
#$.* $.!-!-*(+ -! /?/* .)*/#1 (0#$)/# 24*!- *1 -4!-*(
+-*' (.<!*- 3(+' ?*-/# +0-+*. .*!/#$.**& 3(+' <$/2*-&.)$.
- .*)' ?!4*02$.#/**/#$..*-/*!2*-&$)+-*0/$*)++</#*0"#<4*0
.#*0''**&/*$(+-*1 0+*)/#$.!0)/$*)?
Using the Utility Function
) /#$)"/#/ SQLCipherUtils.decryptTo() - ,0$- .$./#//# *-$"$)')/-" /
*/# 8' .?#/$. 0. $/ )$+# -!*-)-*$*/#) 8' .?
*2 1 -< RandomDatabase.copyTo() !0)/$*)0. .) OutputStream ./-" /<.
/# 0. -$.#**.$)"/#  ./$)/$*)*!/#  3+*-/1$
ActivityResultContracts.CreateDocument<.*2 *)*/#1 8' ?
MANAGING SQLCIPHER
214
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*< RandomDatabase.decryptTo() #. SQLCipherUtils.decryptTo()  -4+//#
/. /*/ (+*--48' <2#$#2 /# )*+4/*/#  .$- OutputStream>
funfun decryptTo(context: ContextContext, stream: OutputStreamOutputStream) {
valval temp = FileFile(context.cacheDir, "export.db")
temp.delete()
SQLCipherUtilsSQLCipherUtils.decryptTo(
context,
context.getDatabasePath(DB_NAMEDB_NAME),
temp,
PASSPHRASEPASSPHRASE.toByteArray()
)
temp.inputStream().copyTo(stream)
temp.delete()
}
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G)*(/. ?&/K
#/24<2 )0. RandomDatabase.decryptTo() /# .( 24/#/2 0. 
RandomDatabase.copyTo()?# /$1$/4<1$ 2(* '<)- +*.$/*-4''!*''*2/#
.( +// -).$)/# *-$"$)' ImportExport ++<%0./-*0/$)"/#  3+*-/A+'$).
/#-*0"#) 2!0)/$*)./#/<$)/#  )</-$"" -''/*
RandomDatabase.decryptTo()?
# - .0'/<$!4*0-0)/# ++)+*+0'/ /# /. </# )*+//* 3+*-//#
+'$)/ 3//. <$./#/4*0- 3+*-/ *+4) *+ ) $)$/ '$ )/
2$/#*0/)4+..+#-. ?
Importing a Plaintext Database
# */# -$- /$*)H$(+*-/$)"+'$)/ 3//. H2*-&.(0#/# .( 24?
*2 1 -</#$./$( <2 '.*) 22-$)&' > / /$)"2# /# -/# /*A A
$(+*-/ /. - ''4$.+'$)/ 3//. *-)*/?
Detecting Plaintext
*( /$( .<4*02$'' $).$/0/$*)2# - 4*0*)*/&)*2$!/# /. $.
'- 4 )-4+/ *-)*/?# 24/* / -($) $!/# /. $. )-4+/ $./*/-4
*+ )$)"$/2$/#/#  (+/4./-$)".+..+#-. ?.2 .2 -'$ -</#/$.#*24*0
/ ''$+# -!*-)-*$/**+ ))0) )-4+/ /. ?!2 ).0 ..!0''4
MANAGING SQLCIPHER
215
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*+ )/# /. 2$/#) (+/4+..+#-. <2 &)*2/#//# /. $.)*/
)-4+/ ?!2 " /.*( .*-/*!+-*' (<2 &)*2/#/ $/# ->
I # /. $. )-4+/ <*-
I # /. $.0) )-4+/ 0//# 8' #. )*--0+/ .*( #*2
*-. 0-$/4- .*).<$+# -!*-)-*$* .)*/$./$)"0$.# /2 )/#*. /2*
. .?#/$..*//& -.))*/' -)!-*(!$' *+ )// (+/J+ -#+.2$/#
)$/ +..+#-. <'$& 12345K2# /# -/# /. $. )-4+/ *-)*/?
# SQLCipherUtils *% /#. getDatabaseState() !0)/$*)/#/++'$ ./#$.
/ #)$,0 <- /0-)$)" State *% /!*-/#- +*..$$'$/$ .>
I # /. $.0) )-4+/ JUNENCRYPTEDK
I # /. $. )-4+/ JENCRYPTEDK
I # /. $.($..$)"JDOES_NOT_EXISTK
0./- ( ( -/#/ ENCRYPTED $..#*-/!*- ENCRYPTED_OR_POSSIBLY_CORRUPT?
* / //#*. . .< getDatabaseState() . .$!4*0-- ,0 ./ 8'  3$./.)<$!$/
* .</-$ .*+ )$)"$/2$/#/#  (+/4+..+#-. >
/**
* The detected state of the database, based on whether we can open it
* without a passphrase.
*/
enumenum classclass StateState {
DOES_NOT_EXISTDOES_NOT_EXIST, UNENCRYPTEDUNENCRYPTED, ENCRYPTEDENCRYPTED
}
funfun getDatabaseState(ctxt: ContextContext, dbPath: FileFile): StateState {
SQLiteDatabaseSQLiteDatabase.loadLibs(ctxt)
ifif (dbPath.exists()) {
varvar db: SQLiteDatabaseSQLiteDatabase? = nullnull
returnreturn trytry {
db = SQLiteDatabaseSQLiteDatabase.openDatabase(
dbPath.absolutePath,
"",
nullnull,
SQLiteDatabaseSQLiteDatabase.OPEN_READONLYOPEN_READONLY
)
db.version
MANAGING SQLCIPHER
216
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
StateState.UNENCRYPTEDUNENCRYPTED
} catchcatch (e: ExceptionException) {
StateState.ENCRYPTEDENCRYPTED
} finallyfinally {
db?.close()
}
}
returnreturn StateState.DOES_NOT_EXISTDOES_NOT_EXIST
}
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
Examining the Utility Function
$1 )+'$)/ 3//. < SQLCipherUtils.encryptTo() 2$'' )-4+/$//*
 .$")/ /. 8' >
funfun encryptTo(
ctxt: ContextContext,
originalFile: FileFile,
targetFile: FileFile,
passphrase: ByteArrayByteArray?
) {
SQLiteDatabaseSQLiteDatabase.loadLibs(ctxt)
ifif (originalFile.exists()) {
valval originalDb = SQLiteDatabaseSQLiteDatabase.openDatabase(
originalFile.absolutePath,
"",
nullnull,
SQLiteDatabaseSQLiteDatabase.OPEN_READWRITEOPEN_READWRITE
)
valval version = originalDb.version
originalDb.close()
valval db = SQLiteDatabaseSQLiteDatabase.openOrCreateDatabase(
targetFile.absolutePath,
passphrase,
nullnull
)
//language=text
valval st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
st.bindString(1, originalFile.absolutePath)
MANAGING SQLCIPHER
217
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
st.execute()
db.rawExecSQL("SELECT sqlcipher_export('main', 'plaintext')")
db.rawExecSQL("DETACH DATABASE plaintext")
db.version = version
st.close()
db.close()
} elseelse {
throwthrow FileNotFoundExceptionFileNotFoundException(originalFile.absolutePath + " not found")
}
}
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
.2$/# decryptTo()< encryptTo() $.$/*(+' 3<0/$/#./# .( .$
./-0/0- ?
./-/4'*$)"$+# -!*-)-*$D.)/$1 '$--$ .0.$)"
SQLiteDatabase.loadLibs(ctxt)?# )<..0($)"/#//# +'$)/ 3//.  3$./.<
2 *+ )$/0.$)"/#  (+/4./-$)".+..+#-. J$)$/$)"$/$.+'$)/ 3/K<" //#
/. 1 -.$*)<)'*. /# /. &0+>
valval originalDb = SQLiteDatabaseSQLiteDatabase.openDatabase(
originalFile.absolutePath,
"",
nullnull,
SQLiteDatabaseSQLiteDatabase.OPEN_READWRITEOPEN_READWRITE
)
valval version = originalDb.version
originalDb.close()
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
/# )*+ )*-- / /#  )-4+/ /. <0.$)"/# .0++'$ +..+#-. >
valval db = SQLiteDatabaseSQLiteDatabase.openOrCreateDatabase(
targetFile.absolutePath,
passphrase,
nullnull
)
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
 3/<2 //#/# +'$)/ 3//. 0.$)" ATTACH DATABASE>
MANAGING SQLCIPHER
218
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
//language=text
valval st = db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
st.bindString(1, originalFile.absolutePath)
st.execute()
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
/# )0. /2*A+-( / -!*-(*! sqlcipher_export()<.4$)"/#/2 2)//*
E 3+*-/F/# +'$)/ 3//. $)/*/#  )-4+/ *) >
db.rawExecSQL("SELECT sqlcipher_export('main', 'plaintext')")
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
$)''4<2  /#/# +'$)/ 3//. <. //# 1 -.$*)$)/#  )-4+/ /. <
)'*.  1 -4/#$)"0+>
db.rawExecSQL("DETACH DATABASE plaintext")
db.version = version
st.close()
db.close()
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
Using the Utility Function
0./. RandomDatabase #.$/. decryptTo() /#/2-+./# SQLCipherUtils
,0$1' )/< RandomDatabase '.*#. encryptFrom() /#/2-+.
SQLCipherUtils.encryptFrom()>
funfun encryptFrom(context: ContextContext, stream: InputStreamInputStream) {
valval temp = FileFile(context.cacheDir, "import.db")
temp.delete()
stream.copyTo(temp.outputStream())
trytry {
whenwhen (SQLCipherUtilsSQLCipherUtils.getDatabaseState(context, temp)) {
SQLCipherUtilsSQLCipherUtils.StateState.UNENCRYPTEDUNENCRYPTED -> SQLCipherUtilsSQLCipherUtils.encryptTo(
context,
temp,
context.getDatabasePath(DB_NAMEDB_NAME),
PASSPHRASEPASSPHRASE.toByteArray()
)
MANAGING SQLCIPHER
219
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
SQLCipherUtilsSQLCipherUtils.StateState.DOES_NOT_EXISTDOES_NOT_EXIST ->
throwthrow IllegalStateExceptionIllegalStateException("Could not find $temp???")
SQLCipherUtilsSQLCipherUtils.StateState.ENCRYPTEDENCRYPTED ->
throwthrow IllegalStateExceptionIllegalStateException("Original database appears encrypted!")
}
} finallyfinally {
temp.delete()
}
}
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G)*(/. ?&/K
*/# getDatabaseState() ) encryptTo() *) SQLCipherUtils 2*-&2$/#8' <
)2 - ./-/$)"2$/#) InputStream?*<2 ./-/4*+4$)"/# *)/ )/.*!
/#/./- (/*/ (+*--4/. 8' ?
/# )'' getDatabaseState()?)/#  3+ / *0/*( <2 " / UNENCRYPTED !*-
/# .// ))/# )'' encryptTo() /* )-4+//# /. )+0/$/$)*0-
 .$- '*/$*)?!2 8))4*/# -.// <2 /#-*2) 3 +/$*)?''*!/#/$.
2-++ $) try G finally<.*2 ) ' / /# / (+*--40) )-4+/ /.
*+4?
About That languagelanguage Comment
)*/# encryptTo() ) decryptFrom() *) SQLCipherUtils<2 #1 )*
*(( )/>
//language=text
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
#$.++ -. !*-  #*!*0- compileStatement() ''.>
//language=text
valval st =
originalDb.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''")
J!-*( (+*-/3+*-/-4+/G.-G($)G%1G*(G*((*).2- G-**(G$(+*-/ 3+*-/G$+# -/$'.?&/K
#$../ (.!-*(! /0- *!)-*$/0$*'' E')"0" $)% /$*)F?.$''4<
/# - - 24.!*-/# /*$)/ -+- /./-$)". $)"..*$/ 2$/#
+-*"-(($)"')"0" )/*+-*1$ .4)/31'$/$*) of the string !*''*2$)"/#
-0' .*!/#/')"0" ?
)/#$.. <4 !0'/</0$*/#$)&./#//# +-( / -/* compileStatement() $.
MANAGING SQLCIPHER
220
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
./-$)"<).*$/2)/./*1'$/ /#/?
#$..*0).2*) -!0'B
*2 1 -</0$*D..4)/3-0' .- . *)$/ <)*/$+# -!*-
)-*$?.- .0'/</0$** .)*/'$& *0- KEY '0. >
Figure 19: Android Studio “Language Injection” SQL Validation
# language=text *(( )/$. #&/*'*&/#/1'$/$*)2-)$)"?
Migrating to Encryption
*-('**(($"-/$*).2*-&2$/#$+# -!*-)-*$<!*-%0./$)"4*0-
/. .# (/**0)/!*-#)" .$)4*0- )/$/$ .<.<).*!*-/#?
*2 1 -<2$/#$+# -!*-)-*$<4*0(4#1 .+ $'E($"-/$*)F/*
*).$ ->($"-/$)"!-*(#1$)")*-$)-4/. /*#1$)") )-4+/ *) ?
#/2*0'*0-$!4*0./-/ 2$/#*-$)-4$/ )*)'4 $ /*
)-4+/$*)'/ -*)?
*<.0++*. 4*0$)$/$''4.#$++ 4*0-++2$/#*-$)-4$/ .1 -.$*)P?O?O?
/ -<$)1 -.$*)Q?O?O*!4*0-++<4*0.#$++ .0++*-/!*- )-4+/ /. .?
*24*0- RoomDatabase 2$'') /*#)' /2*. .>
P? -.$*)Q?O?O*!4*0-++$. $)"$)./'' 4) 20. -?)/#/. <4*0
#1 )* 3$./$)"/. <)4*0)./-/2$/#/#  )-4+/ /.
!-*(/# *0/. /?
Q? ) 3$./$)"P?3?40. -0+"- ./*Q?O?O?# 4'- 4#1 )0) )-4+/ 
/. ?- .0('4</# 42*0''$& /*& +/#//<).*4*0) /*
)-4+//#//. ?
# SQLCipherUtils * /#/2 .2 -'$ -$)/#$.#+/ -)#)' /#$.
. )-$*.2 ''<.2 ). $) /# cryptMigrate +$-*!(*0' . *! /# **&D.
+-$(-4.(+' +-*% /?
#/$- /*-4*)/$)./2*(*0' .? CMBefore $./# .( /*A*++/#/2 .2$)
MANAGING SQLCIPHER
221
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
-'$ -$+# -!*-)-*$.(+' .<%0./2$/#*0/)4 )-4+/$*)?# *- /$''4<
/#$.- +- . )/.1 -.$*)P?O?O*!4*0-++? CMAfter $.(*./'4/# .( ./#
ToDoCrypt .(+' !-*(+- 1$*0.#+/ -.<2# - 2 0. $+# -!*-)-*$
2$/##-* +..+#-. J/*.$(+'$!4/# .(+' K?*2 1 -</#$./$( <
ToDoDatabase #..0./)/$''4(*- *(+' 31 -.$*)*! newInstance()>
packagepackage com.commonsware.todo.repocom.commonsware.todo.repo
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
importimport net.sqlcipher.database.SupportFactorynet.sqlcipher.database.SupportFactory
importimport java.io.IOExceptionjava.io.IOException
privateprivate constconst valval DB_NAME = "stuff.db"
privateprivate constconst valval PASSPHRASE = "sekr1t"
@Database(entities = [ToDoEntityToDoEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass ToDoDatabaseToDoDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun todoStore(): ToDoEntityToDoEntity.StoreStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext): ToDoDatabaseToDoDatabase {
valval dbFile = context.getDatabasePath(DB_NAMEDB_NAME)
valval passphrase = PASSPHRASEPASSPHRASE.toByteArray()
valval state = SQLCipherUtilsSQLCipherUtils.getDatabaseState(context, dbFile)
ifif (state == SQLCipherUtilsSQLCipherUtils.StateState.UNENCRYPTEDUNENCRYPTED) {
valval dbTemp = context.getDatabasePath("_temp.db")
dbTemp.delete()
SQLCipherUtilsSQLCipherUtils.encryptTo(context, dbFile, dbTemp, passphrase)
valval dbBackup = context.getDatabasePath("_backup.db")
ifif (dbFile.renameTo(dbBackup)) {
ifif (dbTemp.renameTo(dbFile)) {
dbBackup.delete()
} elseelse {
dbBackup.renameTo(dbFile)
throwthrow IOExceptionIOException("Could not rename $dbTemp to $dbFile")
}
} elseelse {
MANAGING SQLCIPHER
222
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
dbTemp.delete()
throwthrow IOExceptionIOException("Could not rename $dbFile to $dbBackup")
}
}
returnreturn RoomRoom.databaseBuilder(context, ToDoDatabaseToDoDatabase::classclass.java, DB_NAMEDB_NAME)
.openHelperFactory(SupportFactorySupportFactory(passphrase))
.build()
}
}
}
J!-*( -4+/$"-/ G!/ -G.-G($)G%1G*(G*((*).2- G/**G- +*G**/. ?&/K
./-/4# &$)"/# /. .// 0.$)" getDatabaseState()?!/# .// $.
ENCRYPTED<2 - . /H/#$.$(+'$ ./#//# 0. -#.'- 4-0)/#$.1 -.$*)*!/#
++ !*- )2 - '- 4#1 *0- )-4+/ /. ?!/# .// $.
DOES_NOT_EXIST<2 '.**)*/) /**)4/#$)".+ $'<.**(2$'''54A
- / *0- )-4+/ /. !*-0.?) $/# -*!/#*. . .<2 +-* $- /'4/*
0.$)" Room.databaseBuilder() 2$/#*0- SupportFactory /*- / )G*-*+ )/#
/. ?
# . )-$*2# - 2 ) /** 3/-2*-&$.$!*0-.// $. UNENCRYPTED?#/
( )./# 0. -2.0.$)" CMBefore )#.) 3$./$)"0) )-4+/ /. ?
2)//*& +/# /<0/ )-4+/$/?*<2 >
I . SQLCipherUtils.encryptTo() /* )-4+//# /. /*) 2/.
8'
I  )( /# 0) )-4+/ /. /*/ (+*--4)( JdbBackupK
I  )( /# ) 2'4A )-4+/ /. /**0- .$- )( JdbFileK
I  ' / /# 0) )-4+/ /.
!.*( /#$)""* .2-*)"2$/#*) *!*0-8' G*+ -/$*).<2 /-4/*.2$/#&/*
/# 0) )-4+/ /. )!$'2$/#) 3 +/$*)?
!4*0-0) CMBefore<8''$).*( /*A*$/ (.<)/# )-0) CMAfter<4*02$''8)
/#/4*0-/*A*$/ (.- ($)$)//<0//#//# - .0'/$)"/. $. )-4+/ ?
MANAGING SQLCIPHER
223
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Advanced Scenarios
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Paged Room Queries
*( /$( .<2 .$(+'4#1 /**(0#/?
/$.1 -4 .4/*2-$/ /#/- /0-).''-*2.!-*(+-/$0'-/' ?# /# -
*$)"/#/$.. ).$' *-)*/2$'' + )*)>
I # )0( -*!-*2.$)/#//'
I # )0( -</4+ .<)*)/ )/.*!*'0().$)/#//'
I # $(+/.*!)4.*-*/# -*)./-0/./#/($"#/ 3+)/# - .0'/
. /
!2 &)*2/#/2 ($"#/#1 -$$0'*0.(*0)/*!/<2 )0. **(D.
.0++*-/!*- /#  /+&"$)"'$--4?#$./ # .**(/*- /-$ 1 -*2.E+" F
//$( !-*(/# 0) -'4$)"/' J.K<$)./ *!/# !0''- .0'/. /''/*) ?#$.
)"- /'4- 0 /# (*0)/*!( (*-4/#/$.*).0( /*) <$!2 )
*-")$5 *0-/**)'4) +" D.2*-/#*!//*) ?# *2).$ $./#//#
*)'4 .424/**).0( /#$.+" /$.1$ RecyclerView H)4/#$)" '. $.
/ --$'4*(+'$/ ?
)/#$.#+/ -<2 2$'' 3+'*- $/*0//# "$)"'$--4$)" ) -').#*2
#*2**()"$)")+*+0'/  RecyclerView ./# 0. -.-*''.?
The Problem: Too Much Data
) *!/# '$//' A&)*2)$..0 .2$/#)-*$D.$/ $.#*2/# Cursor 2*-&.?
/ )/*%0./0. /#/ Cursor )$")*-  3/'4#*2$/$." //$)"$/./?#
 #1$*-*!*0-/. Cursor $.)*-('!*-.('' -/. /.0/+*..$'4
+-*' (/$!*-- ''4'-" *) .?
227
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Cursor $.)$)/ -! ?# - '1'../#/2 " /&!-*($/ $.
SQLiteCursor?# Cursor <) SQLiteCursor $)+-/$0'-<2. 1 '*+ 2 ''
 !*- )-*$P?O2.- ' . <)/# - !*- #.!$-.#- *!E! /0- .F/#/
. ( '$& "**$ .//# /$( 0/$)*/#*'0+2 ''./# 4 -.+-*"- .. ?
# *) /#/ 1 -4*4 )*0)/ -.$./# !//#/2# )4*0" / Cursor &!-*(
( /#*.'$& query() *- rawQuery() *) SQLiteDatabase</# ,0 -4#.)*/
/0''4 )*) 4 /?)./ <$/$.'54A 3 0/ 2# )4*0.&/# Cursor !*-
.*( /#$)"2# - /# /$.)  <.0#. getCount()?#$.$.+$)<.2 2)/
/**/# /. G*)&"-*0)/#- <.*2 #1 /*.+ $8''4*
.*( /#$)"2#$' *)/#/&"-*0)/#- J ?"?<'' getCount()K/* ).0- /#/
/# ,0 -4- ''4* ." / 3 0/ 2# )2  3+ /$//*?
)*/# -,0$-&2$/# Cursor $./#/2# )/# ,0 -4$. 3 0/ <$/- ''4+*+0'/ .
CursorWindow?*-.('',0 -$ .</#$.2$''- +- . )//#  )/$- - .0'/. /?*-'-" -
,0 -$ .<$/$.+*-/$*)*!/#/- .0'/. /?.2 (*1 /#-*0"#/# Cursor<
SQLiteCursor 2$'''*(*- - ' 1)/-*2.$)/*/# CursorWindow<-*0)/# ) 2
+*.$/$*)?#$. 3 -/ ./# /#- $)"+-*' (<.2 ($"#/2$)0+*$)"$.&G
/)4+*$)/2#$' 2*-&$)"2$/#/# Cursor<$!/# 2$)*2D.*)/ )/.) /*
%0./ ?
 ''4<4*0-,0 -$ .- .(''<2$/#$)/# CursorWindow '$($/.?)!*-++.2# -
/# /*( .!-*(/# 0. -<0.0''44*0)& +4*0-,0 -$ ..(''?. -.-
*)'4"*$)"/* )/ -$).*(0#/*).(''.- )?1 )$!/# 0. -- *-..*(
!*-(*!(0'/$( $H.0#./&$)"+$/0- 2$/#/# ( -H'-" ,0 -$ .)
 1*$ 4)*/./*-$)"/# ( $$)/# /. $/. '!<0/-/# -./*-$)"$/$)
+'$)8' .- ! - ) 4/# /. ?
*2 1 -<$). .2# - /# /*( .!-*(.*( . -1 -<./$&$)"2$/#.(''
,0 -$ .)" //-$&4?
Addressing the UX
 4*)/# /#- $)"$..0 .</# - $.)*/# -#'' )" 2$/#.#*2$)"'-" - .0'/
. /.$).$)"' J ?"?<$) RecyclerViewK>$/$.+$)!*-0. -./*)1$"/ ?**4
$."*$)"/*2)//*.-*''/#-*0"#PO<OOO-*2.$)1 -/$''4A.-*''$)"'$./H/# $-
8)" -2$'' 1 '*+'$./ -8-./?
!4*0)/$$+/ #1$)"'-" (*0)/*!/<4*0-+-$(-4*) -)$./*" //#
-$"#/?*0.*). -#$)"<8'/ -$)"<)*/# -( ).!*-/# 0. -/* .$'4.*+
/# - ,0$- //*.*( .0. /*!- ' 1) ?*)*/#1 /# +-$(-4 
PAGED ROOM QUERIES
228
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
E.-*''/#-*0"#/# 2*-'F.*-/*! 3+ -$ ) < 1 )$!/#/$.)1$'' *+/$*)!*-
0. -.2#*- "'0//*).!*-+0)$.#( )/*-#1 ./ 'A/$++ 8)" -.J*-<+ -#+.<
./4'0.K?
*2 1 -< 1 )2$/#0. -A.0++'$ *)./-$)/.<4*0./$''($"#/2$)0+2$/#(*-
//#))8/$) CursorWindow?)2 #1 )*$- /*)/-*'*1 -/#/
CursorWindow  #1$*-<.$/$.#$ ) #$)! 2'4 -.*!./-/$*)?
Enter the Paging Library
# "$)"'$--4 3$././*+-*1$ "- / - 1 '*+ -*)/-*'*1 - 3/'42#/" /.
'* !-*(&$)"/./*- )2# )<#)'$)"/#$)".'$& >
I -!*-($)".('' -,0 -$ .</*./4$).$  CursorWindowD.*0).<.*2
)*)/-*'/# /#- .0. !*-/'*.
I 0++*-/$)"(0'/$+' /-1 -.'*+/$*)./#-*0"#/. />)*/*)'4'..$
+*.$/$*)A. .4./ (.<0/*) .2# - 4*0($"#/ )1$"/$)"/- )
) /*- /-$ 1 - '/ #$'*% /..+-/*!/-1 -.'
I 7 -$)"- /$1 ++-*# .<. *) LiveData<.*2 ) ).0- /#/*0-
- ($).- .+*).$1 ?
# "$)"'$--4#. 1*'1 *1 -/# 4 -.?# 0-- )/ $/$*)<&)*2)."$)"
1R<#.+*2 -!0'*/'$)A )/-$<)**(#..$.0++*-/!*-- /0-)$)"
"$)"1RA. - .+*). .!-*(,0 -$ .?
# - - )0( -*!'.. .$)1*'1 $)/# "$)"'$--4<0/!*-.$. )-$*.<
/# - - ! 2*!.$")$8) >
I PagingSource - +- . )/..*0- *!+" /?**() . /0+/*
- /0-) PagingSource !-*( @QueryA))*//  @Dao !0)/$*)<!*- 3(+' ?
I PagingData - +- . )/.#0)&*!/- /-$ 1 !-*(/# 0) -'4$)"/
.*0- 1$/# PagingSource
I Pager - +- . )/..+ $8*+ -/$*)!*-" //$)"+" /*0/*!
PagingSource? )/ # Pager #*2()4$/ (./*- /-$ 1 $)E+" F<
)/# Pager "$1 .0. Flow< LiveData<*-.$($'-- /$1 !*-" //$)"
PagingData *% /..)  ?
I PagingDataAdapter $. RecyclerView.Adapter /#/&)*2.#*2/*2*-&
2$/# PagingData?./# 0. -.-*''./# RecyclerView</#
PagingDataAdapter ).$")'/#/2 ) (*- /</-$"" -$)"/# Pager
/*! /#(*- /!-*(/# PagingSource ) ($/!- .# PagingData !*-
PAGED ROOM QUERIES
229
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/# PagingDataAdapter /*0. ?
#/./$''' 1 .'*/*!(*1$)"+-/.<2#$#2 2$'' 3($) $)"- / - /$'$)/#$.
#+/ -?
Paging and Room
# PagedFTS (*0' *! /# **&D.+-$(-4.(+' +-*% /  (*)./-/ ./# 0. *!
/# "$)"'$--42$/#**(?
#$.$.*0-/#$-" ) -/$*)*!!0''A/ 3/. -#$)".(+' <*-$"$)/$)"2$/# FTS )
*)/$)0$)"2$/# PackagedFTS?)/#*. .(+' .<2 #)++/#/$.+'4 /#
/ 3/*!?? ''.DE# $( #$) F<2$/#$)$1$0'+-"-+#..-*2.$)
RecyclerView?*2 1 -<2 '* /#  )/$- **&J*- )/$- . /*!. -#- .0'/.K/
*) ?E# $( #$) F$..#*-/<0//# - - (0#'*)" -**&.<2# - '*$)"
/#  )/$- **&$)/*( (*-42*0' $(+-/$'?
*< PagedFTS - +' ./# '*A 1 -4/#$)"'*"$*! FTS 2$/#'*A+" .'*"$<
*0-/ .4*!/# "$)"'$--4?
The Dependency
*0. /#*. '.. .<2 ) )*/# - + ) )4<*) !*-/# "$)"'$--4>
implementation "androidx.paging:paging-runtime-ktx:3.0.1"
J!-*( " G0$'?"-' K
androidx.paging:paging-runtime-ktx "$1 .0./# * /#/2 ) /*/ ''**(
/*+-*0 +" ,0 -4- .0'/.)/*+*+0'/  RecyclerView 2$/#/# - .0'/.?.
2$/#*/# - + ) )$ .</# -ktx .093$)$/ ./#//# - - */'$)A.+ $8
!0)/$*).<.0#. toLiveData() 3/ ).$*)!0)/$*)/#/2 2$'' 3($) .#*-/'4?
The DAO
# - 1$.  BookStore $.$/$7 - )//#)2#/2 # !*- >
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport androidx.paging.PagingSourceandroidx.paging.PagingSource
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.Insertandroidx.room.Insert
PAGED ROOM QUERIES
230
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
importimport androidx.room.Queryandroidx.room.Query
@Dao
abstractabstract classclass BookStoreBookStore {
@Insert
abstractabstract suspendsuspend funfun insert(paragraphs: ListList<ParagraphEntityParagraphEntity>)
@Query("SELECT COUNT(*) FROM paragraphs")
abstractabstract suspendsuspend funfun count(): IntInt
@Query("SELECT prose FROM paragraphs ORDER BY sequence")
abstractabstract funfun all(): PagingSourcePagingSource<IntInt, StringString>
@Query("SELECT snippet(paragraphsFts) FROM paragraphs JOIN paragraphsFts "+
"ON paragraphs.id == paragraphsFts.rowid WHERE paragraphsFts.prose "+
"MATCH :search ORDER BY sequence")
abstractabstract funfun filtered(search: StringString): PagingSourcePagingSource<IntInt, StringString>
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&/*- ?&/K
# insert() !0)/$*)$.0)#)" ?# - $.) 2 count() !0)/$*)/#/- /0-).
/# )0( -*!+-"-+#.$)*0-/. ?0//# (*- $)/ - ./$)"#)" .7 /
all() ) filtered()>
I # 4)*'*)" -- suspend !0)/$*).
I # 4)*'*)" -- /0-) List *!+-"-+#.<0/$)./ - /0-)
PagingSource
PagingSource /& ./2*" ) -$/4+ .?# . *)$./# /4+ *!//#/2 -
+"$)"/#-*0"#?)/#$.. <*/#,0 -$ .- /0-).$(+' String *% /.<.**0-
. *)//4+ /* PagingSource $. String? *0'0. ParagraphEntity *-
)4/#$)" '. **(&)*2.#*2/*#)' ?
# 8-.///4+ $)/# PagingSource  '-/$*)$)$/ ./*/# "$)"'$--4
#*22 - $ )/$!4$)"/# *)/ )/.*!+" .?**(2$''*/#$.. *)+*.$/$*)
2$/#$)- .0'/. /<.*/# 8-./+" ($"#/ -*2.OASX</# . *)+" ($"#/
-*2.TOAXX<).**)?$/#**(<+*.$/$*).- $)$/ 4) Int<.*/# 8-./
/4+ /#/2 +-*1$ /* PagingSource $.) Int?# "$)"'$--4.0++*-/.*/# -
./-/ "$ .<(*./'4 .$") -*0) 1 '*+ -.- /$)"0./*(/.*0- .J ?"?<
2-++ -*0)A./4'  . -1$ K<0/**(0. .+*.$/$*).?
.&$+/# suspend & 42*- 0. PagingSource * .)*/+ -!*-()4G
$(( $/ '4?)./ </# /0'G$. '4 0)/$'.*( /#$)"./-/.0.$)"/#
PAGED ROOM QUERIES
231
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
!/*-4?#$.$.(0#'$& #*2 LiveData< Single<) Flow 2*-&2$/# *0-'..$
- /$1 - /0-)/4+ .?
The ViewModels
BookViewModel ) SearchViewModel ) /* 3+*. /# **&*)/ )/./*/# $-
- .+ /$1 '4 -.?)/# *-$"$)'+-*% /</#$.2.1$ LiveData?)</# "**
) 2.$./#/2 )" / LiveData !-*( PagingSource H/#*0"#2#$' /# * $.
/ -. <$/$.$/*(+'$/ >
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport androidx.lifecycle.ViewModelandroidx.lifecycle.ViewModel
importimport androidx.lifecycle.viewModelScopeandroidx.lifecycle.viewModelScope
importimport androidx.paging.Pagerandroidx.paging.Pager
importimport androidx.paging.PagingConfigandroidx.paging.PagingConfig
importimport androidx.paging.cachedInandroidx.paging.cachedIn
importimport androidx.paging.liveDataandroidx.paging.liveData
classclass BookViewModelBookViewModel(repo: BookRepositoryBookRepository) : ViewModelViewModel() {
valval paragraphs =
PagerPager(PagingConfigPagingConfig(pageSize = 15)) { repo.all() }
.liveData
.cachedIn(viewModelScope)
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&$ 2* '?&/K
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport androidx.lifecycle.ViewModelandroidx.lifecycle.ViewModel
importimport androidx.lifecycle.viewModelScopeandroidx.lifecycle.viewModelScope
importimport androidx.paging.Pagerandroidx.paging.Pager
importimport androidx.paging.PagingConfigandroidx.paging.PagingConfig
importimport androidx.paging.cachedInandroidx.paging.cachedIn
importimport androidx.paging.liveDataandroidx.paging.liveData
classclass SearchViewModelSearchViewModel(search: StringString, repo: BookRepositoryBookRepository) : ViewModelViewModel() {
valval paragraphs =
PagerPager(PagingConfigPagingConfig(pageSize = 15)) { repo.filtered(search) }
.liveData
.cachedIn(viewModelScope)
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G -#$ 2* '?&/K
PAGED ROOM QUERIES
232
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
.)*/  -'$ -< Pager #)' ." //$)"+" .*!/!-*(*0- PagingSource?
)*)8"0- /# Pager 1$/# PagingConfig +-( / -H# - <2 .4/#/2
2)//*'*PT+-"-+#.//$( <1$ pageSize? '.*+-*1$ /# Pager 2$/#
/# PagingSource 424*!'( 3+- ..$*)?# )2 ./-//-4$)"/*" //
!-*(/# Pager<$/2$''$)1*& /#/'( 3+- ..$*)<" //# PagingSource<)
./-/.&$)"$/!*-/?
#/. /.0+/# Pager $/. '!?# liveData 3/ ).$*)+-*+ -/4*) Pager "$1 .0.
LiveData 2-++ --*0)/# Pager@*-<(*- 0-/ '4< LiveData 2-++ -
-*0) Flow /#/$.2-++ -*0)/# Pager?#$. LiveData 2$'' !*-
PagingData *!*0- PagingSource //4+ H$)/#$.. < String?# Pager<2# )
$/$.- ,0 ./ /*'*$7 - )/+" .<2$'' ($/!- .# PagingData #*'$)"/#
- .0'/.?
 0. /#$. LiveData $.& 4 Flow<2 ) /*/ #/# LiveData *0//#
CoroutineScope /*0. <.*/#//# Flow *'' /*-/#/$/0. .#)' .'$! 4' .
+-*+ -'42$/#$)/# *-*0/$) .4./ (?#$.$.#)' 4''$)" cachedIn() )
+-*1$$)".0$/' .*+ H$)/#$.. < viewModelScope?!4*0!*-" //**/#$.<
2#$' /# * 2$''*(+$' < 4*02$''#1 /$( 2# )4*0/-4/*-0)$/?
The PagingDataAdapterPagingDataAdapter
- .#*2$)"*0-**&*)/ )/.$) ParagraphAdapter?)/# *-$"$)'+-*% /<
/#$.2..$(+' RecyclerView.Adapter?!4*0- 0.$)"/# "$)"'$--4</#*0"#<
4*02$''2)//*0. PagingDataAdapter?*0)#) PagingDataAdapter
PagingData *!/J ?"?< PagingData *!+-"-+#.K<) PagingDataAdapter
&)*2.#*2/*0. /# PagingData /*#)' '*$)"$/$*)'+" ../# 0. -
.-*''.?
# "**) 2.$./#/2$/# PagingDataAdapter<4*0*)*/) /**/# -2$/#
$(+' ( )/$)" getCount()<. PagingDataAdapter &)*2.$/. PagingData ))" /
/# *1 -''.$5 !-*($/?# ) 2.$./#/4*02$'') /*+-*1$ 
DiffUtil.ItemCallback /**(+- *% /.$)/# '$./?!4*0#1 0. /#
RecyclerView 1 -.$*)*! ListAdapter<$/0. ./# .( DiffUtil.ItemCallback
./-/'../#/ PagedListAdapter * .?
*<*0-- 1$.  ParagraphAdapter '**&.'$& >
packagepackage com.commonsware.room.ftscom.commonsware.room.fts
importimport android.view.LayoutInflaterandroid.view.LayoutInflater
PAGED ROOM QUERIES
233
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
importimport android.view.ViewGroupandroid.view.ViewGroup
importimport androidx.paging.PagingDataAdapterandroidx.paging.PagingDataAdapter
importimport androidx.recyclerview.widget.DiffUtilandroidx.recyclerview.widget.DiffUtil
classclass ParagraphAdapterParagraphAdapter(privateprivate valval inflater: LayoutInflaterLayoutInflater) :
PagingDataAdapterPagingDataAdapter<StringString, RowHolderRowHolder>(STRING_DIFFERSTRING_DIFFER) {
overrideoverride funfun onCreateViewHolder(parent: ViewGroupViewGroup, viewType: IntInt) =
RowHolderRowHolder(inflater.inflate(RR.layout.row, parent, falsefalse))
overrideoverride funfun onBindViewHolder(holder: RowHolderRowHolder, position: IntInt) {
holder.bind(getItem(position).orEmpty())
}
}
privateprivate valval STRING_DIFFER = objectobject : DiffUtilDiffUtil.ItemCallbackItemCallback<StringString>() {
overrideoverride funfun areItemsTheSame(oldItem: StringString, newItem: StringString) =
oldItem === newItem
overrideoverride funfun areContentsTheSame(oldItem: StringString, newItem: StringString) =
oldItem == newItem
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G-"-+#+/ -?&/K
 - < STRING_DIFFER $. DiffUtil.ItemCallback /#/)#)' *(+-$)".$(+'
./-$)".<0.$)"*)/ )/ ,0'$/4!*- areContentsTheSame() )*% / ,0'$/4J=== $)
*/'$)K!*- areItemsTheSame()?
# - $.'.**) */# -.0/' $7 - ) >/# //#/2 $)$)/*/# RowHolder?
)/# *-$"$)'++</#$.2. String?)/#$.++</#*0"#<$/$. String??#
"$)"'$--40. . null ./#  !0'/+' #*' -/<$!2 /-4 ..$)"+-/.*!
/# '$.//#/#1 )*/4 / )'* ? '' getItem() !-*( PagingDataAdapter
/*- /0-)/# /!*-"$1 )+*.$/$*)<)$/2$''- /0-))0''' /4+ ? #1 /*
 ' /**+ 2$/# null 1'0 ? - <2 %0./0. orEmpty() /**)1 -/$/$)/*)
(+/4./-$)"<0/(*- .*+#$./$/ ++($"#/0. $7 - )/( #)$.(/*
$)$/  RecyclerView $/ (/#/#. )'*$)"?) /# /!*-/#/+*.$/$*)
$.1$'' < onBindViewHolder() 2$'' '' "$)<.*4*0)- +*+0'/ /# 
!*-/#/$/ (2$/#/# )*2A1$'' /?
The Fragments
# *)'4.$")$8)/$7 - ) $)/# !-"( )/.$.$). //$)"0+/#
ParagraphAdapter?)/# *-$"$)'+-*% /<2 2*0'+..$)/# List<String> /*/#
ParagraphAdapter *)./-0/*-<.*2 #/*2$//*- / /# ParagraphAdapter
PAGED ROOM QUERIES
234
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
0)/$'2 2 - *. -1$)"/# LiveData /#/2*0'+-*1$ *0- List?*2<2 ''
submitData() ( /#**) ParagraphAdapter<.0++'$ 4 PagingDataAdapter<.*2
). /0+/# +/ -# *!/$( >
supersuper.onViewCreated(view, savedInstanceState)
valval rv = view asas RecyclerViewRecyclerView
valval adapter = ParagraphAdapterParagraphAdapter(layoutInflater)
rv.adapter = adapter
vm.paragraphs.observe(viewLifecycleOwner) {
adapter.submitData(viewLifecycleOwner.lifecycle, it)
}
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G**&-"( )/?&/K
supersuper.onViewCreated(view, savedInstanceState)
valval rv = view asas RecyclerViewRecyclerView
valval adapter = ParagraphAdapterParagraphAdapter(layoutInflater)
rv.adapter = adapter
vm.paragraphs.observe(viewLifecycleOwner) {
adapter.submitData(viewLifecycleOwner.lifecycle, it)
}
}
}
J!-*( " G.-G($)G%1G*(G*((*).2- G-**(G!/.G -#-"( )/?&/K
-*(/# 0. -D../)+*$)/</# - $.)*- '$7 - ) $) #1$*-?# 0. -)
.-*''/#-*0"#/# '$./)- /# **&*-/# . -#- .0'/.?)+-$)$+' <*).'*2
 1$ </# 0. -($"#/.-*''!./ )*0"#/#//# /$.)*/- 4<).*')&
.+*/2*0'++ -//# *//*(*!/# .-*'' - J-*2.2$/#/#  (+/4./-$)"K
0)/$'/# /"*/'* ?)/# . *!/#$.++</# /. G$..('')!$-'4
,0$&<.*/# 0. -$.0)'$& '4/* )*0)/ -)4.0#1$.0'#$0+2$/#-+$
.-*''$)"? /<*0-++2$'' (*-  9$ )/$)$/.0. *!( (*-4<4)*/)  ..-$'4
'*$)"/#  )/$- **&/*) ?
PAGED ROOM QUERIES
235
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Room Across Processes
*./)-*$++.-0)$).$)"' +-* ..?*2 1 -<)-*$* ."$1  1 '*+ -.
/# *+/$*)/*#1 /# $-++. .+'$/-*..(0'/$+' +-* .. .?
*2 1 -</#$.)0. +-*' (.2$/#./07# '$)( (*-4<.(0'/$+' +-* .. .
2$'')*/.#- /#/( (*-4?)/# . *!**(<*) - 2# - /# +-*' (-*+.
0+$.2$/#E$)1'$/$*)/-&$)"F>/# +$  *!**(/#/&)*2./*0+/ 4*0-
- /$1 ,0 -4- .0'/.JFlow< LiveData< Observable< /?K2# )/# /#)" .?4
 !0'/</#/* .)*/2*-&-*..+-* ..*0)-$ .<0/4*0) )' $/1$
enableMultiInstanceInvalidation() *)4*0- RoomDatabase.Builder?
)/#$.#+/ -<2 2$'' 3+'*- ''*!/#$.$)"- / - /$') (*)./-/ #*2$/
2*-&.?
Room and Invalidation Tracking
# )4*0(*$!44*0-**(/. </$1 *. -1 -.*!- /$1 - .+*). .!-*(
@QueryA))*// !0)/$*)." /!- .#- .0'/. '$1 - 0/*(/$''4?
) -/# *1 -.</#/$.+*2 - 4 InvalidationTracker )- '/ * ?**(
/-&.>
I #$#/' .)1$ 2.- - ! - ) 4/$1 ,0 -$ .<)
I #$#/' .- 7 / 4/. (*$8/$*).
# )4*0(*$!4/# /. <**('**&.0+/# /$1 ,0 -$ ./#/- /$ /*
)4/' ./#/4*07 / ?*-/#*. <**(- A 3 0/ ./# ,0 -$ .) ($/.) 2
- .0'/.1$/# Flow< LiveData< Observable<*-2#/ 1 -?
237
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
#$.$.''*) !*-4*0<'(*./4("$?0/<$)- '$/4<$/%0./.*+#$./$/ *
!-*(.*+#$./$/ '$--4?
Invalidation Tracking and Processes
InvalidationTracker $.)*-$)-41'..?*- RoomDatabase )/# * A
" ) -/ $/./#/2 " /!-*(/# **(*(+$' -?# 4- +-/*!/# ( (*-4*!
2#/ 1 -+-* ../#/4*00. /*- / )$)./) *!4*0- RoomDatabase .0'..?
!4*0#1 /2*+-* .. .< #2*-&$)"2$/#/# .( RoomDatabase .0'..< #
+-* ..2$''#1 $/.*2)$) + ) )/$)./) *!/#/.0'..<) #*!/#*.
2$'' ..*$/ 2$/#$/.*2) InvalidationTracker?#*!/#*. $)./) .2$''
&)*2)*/#$)"*0//# */# -?
!4*0(*$!4/# /. $)*) +-* ..</# InvalidationTracker *!/#/+-* ..
))*/$!4*. -1 -.$)/#/+-* ..*0/0+/ - .0'/./*/# $-,0 -$ .?*2 1 -<
4 !0'/</# InvalidationTracker *!/# */# -+-* ..2$'')*/8)*0/*0//#
/. (*$8/$*).)2$'')*/ ' /*0+/ $/.*2)*. -1 -.2$/#!- .#
/?
Introducing enableMultiInstanceInvalidation()enableMultiInstanceInvalidation()
**()*2#.) enableMultiInstanceInvalidation() !0)/$*)/#/4*0)''
*) RoomDatabase.Builder 2# )4*0- . //$)"0+/# /. ?#$./ ''.**(
/#/4*02)//*0. $/-*..+-* .. .?**(2$''/# ). /0+
MultiInstanceInvalidationService $)4*0-+-$(-4J !0'/K+-* ..?
RoomDatabase *% /.$)*/# -+-* .. .2$''*)) //*/#/. -1$ <)**(2$''
0. /*''*2/. (*$8/$*)$)!*-(/$*)/*:*2 /2 )/# +-* .. .?
# ) / 7 /$./#/ # InvalidationTracker 8).*0/*0/(*$8/$*).
#++ )$)"$))4*!/# ++D.+-* .. .?
# CrossProcess (*0' *! /# **&D.+-$(-4.(+' +-*% / #./. /#/
0. . enableMultiInstanceInvalidation()>
packagepackage com.commonsware.room.processcom.commonsware.room.process
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
ROOM ACROSS PROCESSES
238
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
privateprivate constconst valval DB_NAME = "random.db"
@Database(entities = [RandomEntityRandomEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass RandomDatabaseRandomDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun randomStore(): RandomStoreRandomStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, RandomDatabaseRandomDatabase::classclass.java, DB_NAMEDB_NAME)
.enableMultiInstanceInvalidation()
.build()
}
}
J!-*( -*..-* ..G.-G($)G%1G*(G*((*).2- G-**(G+-* ..G)*(/. ?&/K
*-/#//* 0. !0'</#*0"#<2 ) (*- /#)*) +-* ..<)2 ) !*- #
+-* ../* 2*-&$)"2$/#/# .( 0) -'4$)"$/ /. 1$/# .(
**(A" ) -/ '.. .?
In One Process, an ActivityActivity
)/# ($)++'$/$*)+-* ..< MainActivity #.- ''4$"E*+0'/ (+'
/F0//*)/#/<2# )'$& <''. populate() !0)/$*)*) MainViewModel?
#/$)/0-)''. populate() *)/# RandomRepository?#/" ) -/ .-)*(
)0( -*! RandomEntity $)./) .)$). -/./# (>
packagepackage com.commonsware.room.processcom.commonsware.room.process
importimport kotlinx.coroutines.CoroutineScopekotlinx.coroutines.CoroutineScope
importimport kotlinx.coroutines.asCoroutineDispatcherkotlinx.coroutines.asCoroutineDispatcher
importimport kotlinx.coroutines.withContextkotlinx.coroutines.withContext
importimport java.util.concurrent.Executorsjava.util.concurrent.Executors
importimport kotlin.random.Randomkotlin.random.Random
classclass RandomRepositoryRandomRepository(
privateprivate valval db: RandomDatabaseRandomDatabase,
privateprivate valval appScope: CoroutineScopeCoroutineScope
) {
privateprivate valval dispatcher =
ExecutorsExecutors.newSingleThreadExecutor().asCoroutineDispatcher()
funfun summarize() = db.randomStore().summarize()
ROOM ACROSS PROCESSES
239
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
suspendsuspend funfun populate() {
withContext(dispatcher + appScope.coroutineContext) {
valval count = RandomRandom.nextInt(100) + 1
db.randomStore().insert((1..count).map { RandomEntityRandomEntity(0) })
}
}
}
J!-*( -*..-* ..G.-G($)G%1G*(G*((*).2- G-**(G+-* ..G)*( +*.$/*-4?&/K
RandomRepository '.*#. summarize() !0)/$*)/#/ 3+*. .*-- .+*)$)"
!0)/$*)*)*0->
packagepackage com.commonsware.room.processcom.commonsware.room.process
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.Insertandroidx.room.Insert
importimport androidx.room.Queryandroidx.room.Query
importimport kotlinx.coroutines.flow.Flowkotlinx.coroutines.flow.Flow
importimport java.time.Instantjava.time.Instant
data classdata class SummarySummary(
valval count: IntInt,
valval oldestTimestamp: InstantInstant? = nullnull
)
@Dao
interfaceinterface RandomStoreRandomStore {
@Insert
suspendsuspend funfun insert(entities: ListList<RandomEntityRandomEntity>)
@Query("SELECT COUNT(*) as count, MIN(timestamp) as oldestTimestamp FROM randomStuff")
funfun summarize(): FlowFlow<SummarySummary>
}
J!-*( -*..-* ..G.-G($)G%1G*(G*((*).2- G-**(G+-* ..G)*(/*- ?&/K
summarize() " /./# *0)/*! )/$/$ .)/# *' .//$( ./(+) ($/./# (1$
Flow?#/ Flow 2$'' ($/) 2- .0'/../# /. $.(*$8 ).*'*)".
.*( /#$)"$.*. -1$)"/# Flow? MainActivity " /./#//1$ MainViewModel
).#*2./# *0)/)/ *)/# .- )?
In Another Process, a ServiceService
0-()$! ./#. <service> )/-4!*- SomeService<+'$)"$/$)/*)*/# -+-* ..
1$ android:process>
<service<service android:name=".SomeService" android:process=":something" />/>
J!-*( -*..-* ..G.-G($)G)-*$)$! ./?3('K
ROOM ACROSS PROCESSES
240
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
SomeService '.*0. . summarize() *) RandomRepository<0(+$)"2#/ 1 -$/
-  $1 ./**"/>
packagepackage com.commonsware.room.processcom.commonsware.room.process
importimport android.content.Intentandroid.content.Intent
importimport android.os.Processandroid.os.Process
importimport android.util.Logandroid.util.Log
importimport androidx.lifecycle.LifecycleServiceandroidx.lifecycle.LifecycleService
importimport kotlinx.coroutines.CoroutineScopekotlinx.coroutines.CoroutineScope
importimport kotlinx.coroutines.flow.collectkotlinx.coroutines.flow.collect
importimport kotlinx.coroutines.launchkotlinx.coroutines.launch
importimport org.koin.android.ext.android.injectorg.koin.android.ext.android.inject
importimport org.koin.core.qualifier.namedorg.koin.core.qualifier.named
classclass SomeServiceSomeService : LifecycleServiceLifecycleService() {
privateprivate valval repo: RandomRepositoryRandomRepository byby inject()
privateprivate valval appScope: CoroutineScopeCoroutineScope byby inject(named("appScope"))
overrideoverride funfun onCreate() {
supersuper.onCreate()
appScope.launch {
repo.summarize().collect {
LogLog.d("SomeService", "PID: ${Process.myPid()} summary: $it")
}
}
}
}
J!-*( -*..-* ..G.-G($)G%1G*(G*((*).2- G-**(G+-* ..G*(  -1$ ?&/K
MainActivity ./-/./#/. -1$ 2# )$/$.$)/# !*- "-*0)1$ onStart() )
./*+./#/. -1$ 2# )/# - /0-)./*/# &"-*0)$) onStop()?#$.$.)*/
2$. 0. *!. -1$ =/# +*$)/ #$). -1$ $./*-0)2# )/# $. not $)/#
!*- "-*0)?0/<$/# '+.$''0./-/ /#  7 /.*!
enableMultiInstanceInvalidation()?
Results, Before and After
!2 '&  enableMultiInstanceInvalidation() H.0#.$!4*0*(( )/*0/
/#/'$) $) RandomDatabase )-0)/# ++H4*02$''8)/#/ SomeService '*".
/# $)$/$'.// *!/# /. <0//#/$.$/?*0)+0.#/# $"0//*).(0#
.4*02)/<)/# /$1$/42$''$.+'4/# 0-- )/*0)/*! )/$/$ .<0//#
. -1$ 2$'')*/'*") 2/?#/$. 0. *0-. +-/ +-* ..* .)*/&)*2
ROOM ACROSS PROCESSES
241
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/#//# /. #)" <.***(* .)*/ ($/) 2- .0'/*)/#/+-* ..D
summarize() Flow?
0/<$!2 0. enableMultiInstanceInvalidation()<)*2'$&$)"/# 0//*)0. .
*/#/# /$1$/4)/# . -1$ /*" / /$'.*!/# 0+/ /. <.*2 .
*/#/# 0+/ )/# SomeService *"/ )/-4?
ROOM ACROSS PROCESSES
242
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Triggers
-$"" -.- 24/*/ #$/ /*()$+0'/ '  1 -4/$( /#/4*0*
.*( /#$)"/*' ?#$' /*/ **"' #. ' / /*)*/.0++*-//-$"" -.
$- /'4$)**(<4*0)./$''. //# (0+()0''4$!)  ?
)/#$.#+/ -<2 2$'' 3($) #*2/**/#$.?
Trigger Basics
-$"" -.- (*./*!/ )..*$/ 2$/#. -1 -A.$ /. .>-' < -1 -<
).**)?) /-$"" -.#1 /# $-*2).$ .?*2 1 -</# 4- )1$'' *+/$*)
*)$/ <2#$#.0++*-/. /# CREATE TRIGGER .// ( )/ /* 8) /-$"" -?
*0)/#$)&*!/-$"" -.. $)")E$!/#$.</# )/#/F.*-/*!*)./-0/>
I !2 $). -/-*2$)' <0+/ .*( /$)' 
I !2 (*$!4-*2$)' <'.*(*$!4- '/ -*2.$)' 
I !2  ' / /!-*(' <$). -/-*2$)' 
I ).**)
/#$"#' 1 '</# .4)/3!*-- /$)"/-$"" -$.>
CREATECREATE TRIGGERTRIGGER [name] [timing] [action] ONON [tabletable]
BEGINBEGIN
[SQLSQL statements]
ENDEND;
#/-$"" -#.)( </# .( 24/#//' .)1$ 2.#1 )( .?# /$($)"
0.0''4$. BEFORE *- AFTER<)/# /$*)$. INSERT< DELETE<*- UPDATE OF
[columns]<2$/#/# '// -- +- . )/$)"(*$8/$*)*!.*( *'0()J.K*)*) *-
243
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
(*- -*2.?*<4*0" /*($)/$*).'$& >
I BEFORE INSERT
I AFTER UPDATE OF name
I BEFORE DELETE
I ).**)
# .// ( )/. /2 ) BEGIN ) END; 2$''  3 0/ 2# ) 1 -/# /$*)
*0-.*)/# .+ $8 /' < $/# - BEFORE *- AFTER /# /$*)$/. '!$.+ -!*-( ?
*< BEFORE 3 0/ ./# .// ( )/. !*- /# /$*)$.++'$ /*/# /' <
2#$' AFTER 3 0/ ./# .// ( )/.!/ -/# /$*)$.++'$ /*/# /' ?
Room and Triggers
 1 '*+ -..& !*-**"' /**7 -8-./A'...0++*-/!*-/-$"" -.&$)QOPV= /#$.
- ,0 ./2.- % / ?
*2 1 -< )*/# -- ,0 ./$../$''*+ )?#/*0' .$")/#/**(($"#/
.0++*-//-$"" -.$- /'4$)/# !0/0- ?-<$/*0' .$")/#//# $..0 /-& -
#../' $..0 .?
 "-' ..<.*!**(Q?S?O</# - $.)* @Trigger ))*//$*)/*/ #**(*0/
/-$"" -.?
)./ <2 #1 /**$//# #-24?
Triggers the Hard Way
*<&$)QOPV< ( $''42*-& *0//# .$( #)$.*!. //$)"0+
/-$"" -<40.$)"/# onCreate() ''&!0)/$*)*) RoomDatabase.Callback?
# Trigger (*0' *! /# **&D.+-$(-4.(+' +-*% /  (*)./-/ ./#$.
++-*#?
)/#$.+-*% /<2 #1 /2***(A()" /' .>
I randomStuff<*)/$)$)".*( -)*( )/$/$ ./#/2 $). -/
I countOfRandomStuff<2#$#*)/$)./# *0)/*!/# )0( -*! )/$/$ .$)
randomStuff</#/!*-.*( ./-)" - .*)2 2)//*./*- $). +-/
/'
TRIGGERS
244
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*& + countOfRandomStuff 0+/*/ 2$/#- .+ //* )/$/$ . $)"$). -/ $)/*
randomStuff<2 *0'0. /# !*''*2$)"/-$"" ->
CREATECREATE TRIGGERTRIGGER updateCount AFTERAFTER INSERTINSERT ONON randomStuff
BEGINBEGIN
UPDATEUPDATE countOfRandomStuff SETSET countcount = (SELECTSELECT COUNTCOUNT(*) FROMFROM randomStuff);
ENDEND;
J$)+-$)$+' <2 '.*) ) AFTER DELETE /-$"" -<0/.$) /#$..(+' ++) 1 -
 ' / ./!-*( randomStuff<2 ).&$+$/K
# UPDATE countOfRandomStuff .// ( )/2$''0+/ !/ -*) *-(*- -*2.-
$). -/ $)/* randomStuff?# UPDATE countOfRandomStuff .// ( )/0+/ . all
*!$/.-*2./*#1  count *'0()- : //# *0)/*!-*2.$) randomStuff?.$/
/0-).*0/<2 2$''*)'4#1 *) -*2$)/#/ countOfRandomStuff /' ?
. /0+ countOfRandomStuff 0.$)"**( @Entity>
packagepackage com.commonsware.room.triggercom.commonsware.room.trigger
importimport androidx.room.Daoandroidx.room.Dao
importimport androidx.room.Entityandroidx.room.Entity
importimport androidx.room.PrimaryKeyandroidx.room.PrimaryKey
importimport androidx.room.Queryandroidx.room.Query
@Entity(tableName = "countOfRandomStuff")
data classdata class CountEntityCountEntity(
@PrimaryKey(autoGenerate = truetrue) valval id: LongLong,
valval count: IntInt
) {
@Dao
interfaceinterface StoreStore {
@Query("SELECT count FROM countOfRandomStuff LIMIT 1")
suspendsuspend funfun getCurrent(): IntInt
}
}
J!-*( -$"" -G.-G($)G%1G*(G*((*).2- G-**(G/-$"" -G*0)/)/$/4?&/K
#/ CountEntity '.*#.) ./ $)/ -! < .-$$)" getCurrent()
!0)/$*)/#/2$''- /0-)/# count 1'0 !*-/# 8-./-*2$)/# countOfRandomStuff
/' ?
RandomDatabase )*/*)'4#**&.0+*/#/# CountEntity )/# RandomEntity
J/#/ 8) ./# randomStuff /' K<0/$/'.** ..*(  3/-2*-&2# )/#
TRIGGERS
245
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/. $.- / >
packagepackage com.commonsware.room.triggercom.commonsware.room.trigger
importimport android.content.Contextandroid.content.Context
importimport androidx.room.Databaseandroidx.room.Database
importimport androidx.room.Roomandroidx.room.Room
importimport androidx.room.RoomDatabaseandroidx.room.RoomDatabase
importimport androidx.room.TypeConvertersandroidx.room.TypeConverters
importimport androidx.sqlite.db.SupportSQLiteDatabaseandroidx.sqlite.db.SupportSQLiteDatabase
privateprivate constconst valval DB_NAME = "random.db"
@Database(entities = [RandomEntityRandomEntity::classclass, CountEntityCountEntity::classclass], version = 1)
@TypeConverters(TypeTransmogrifierTypeTransmogrifier::classclass)
abstractabstract classclass RandomDatabaseRandomDatabase : RoomDatabaseRoomDatabase() {
abstractabstract funfun randomStore(): RandomStoreRandomStore
abstractabstract funfun countStore(): CountEntityCountEntity.StoreStore
companioncompanion objectobject {
funfun newInstance(context: ContextContext) =
RoomRoom.databaseBuilder(context, RandomDatabaseRandomDatabase::classclass.java, DB_NAMEDB_NAME)
.addCallback(objectobject : RoomDatabaseRoomDatabase.CallbackCallback() {
overrideoverride funfun onCreate(db: SupportSQLiteDatabaseSupportSQLiteDatabase) {
supersuper.onCreate(db)
db.execSQL("INSERT INTO countOfRandomStuff (count) VALUES (0);")
db.execSQL(
"""
CREATE TRIGGER updateCount AFTER INSERT ON randomStuff
BEGIN
UPDATE countOfRandomStuff SET count = (SELECT COUNT(*) FROM randomStuff);
END;
""".trimIndent()
)
}
})
.build()
}
}
J!-*( -$"" -G.-G($)G%1G*(G*((*).2- G-**(G/-$"" -G)*(/. ?&/K
0. addCallback() ) RoomDatabase.Callback *% //*" /*)/-*'2# )/#
/. $.- / <1$ onCreate()?)/# - <2 */2*/#$)".>
P? ). -/.$)"' -*2$)/* countOfRandomStuff<.**0-/-$"" -'24.#.-*2
TRIGGERS
246
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
/*0+/ =)
Q? 3 0/ ./# CREATE TRIGGER .#*2) -'$ -$)/#$.#+/ -</* 8) *0-
/-$"" -
*2< 1 -4/$( /#/2 $). -/-*2.$)/* randomStuff< countOfRandomStuff 2$''" /
0+/ ? ) ../# countOfRandomStuff /1$ CountEntity )$/.<
%0./'$& )4*/# -**(A()" /' ?#/$. 0. countOfRandomStuff is
**(A()" /' <%0./*) 2#*. *)/ )/.- . /0+1$/# onCreate()
''&)/# /-$"" -<-/# -/#)1$''.*)/# $/. '!?
TRIGGERS
247
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
What’s New in Room?
**(& +.#)"$)"<$)"$)) 2! /0- .2$/# 1 -4(%*-)($)*-- ' . ?
#$.#+/ -#$"#'$"#/..*( *!/#*. #)" .?
Version 2.3.x
+-$'QOQP.2/# - ' . *! 2.3.0?.*!'/ /* -QOQP<)*) 2+/#- ' . .
#1  )- ,0$- ?
2.3.0 "1 0.0)#*!$(+-*1 ( )/.<+-/$0'-'4-*0)/4+ )*'0()
()" ( )/?
Enum Support
$./*-$''4<$!4*0- @Entity #+-*+ -/4/#/2..*( enum class<4*0)  
/*2-$/ /4+ *)1 -/ - /**)1 -//#/ )0(/*)!-*(.*( /#$)" '. <.0#.
) Int *- String?#/$../$'')*+/$*)?
*2 1 -<**( 2.3.0  0/*(/$*)1 -.$*)*!) enum class 1'0 /*)
!-*($/. String - +- . )//$*)?*<)*24*0)0. ) )0(2$/#*0/
@TypeConverter +$->
packagepackage com.commonsware.room.misccom.commonsware.room.misc
importimport androidx.room.*androidx.room.*
enumenum classclass SillySilly { FirstFirst, SecondSecond, ThirdThird }
@Entity(tableName = "autoEnum")
data classdata class AutoEnumEntityAutoEnumEntity(
249
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@PrimaryKey(autoGenerate = truetrue)
varvar id: LongLong,
varvar silly: SillySilly
) {
@Dao
abstractabstract classclass StoreStore {
@Query("SELECT * FROM autoEnum")
abstractabstract funfun loadAll(): ListList<AutoEnumEntityAutoEnumEntity>
@Query("SELECT * FROM autoEnum WHERE id = :id")
abstractabstract funfun findById(id: IntInt): AutoEnumEntityAutoEnumEntity
funfun insert(entity: AutoEnumEntityAutoEnumEntity): AutoEnumEntityAutoEnumEntity {
entity.id = _insert(entity)
returnreturn entity
}
@Insert
abstractabstract funfun _insert(entity: AutoEnumEntityAutoEnumEntity): LongLong
}
}
J!-*( $.(+' .G.-G($)G%1G*(G*((*).2- G-**(G($.G0/*)0()/$/4?&/K
)/#$.. <*0- silly *'0()$. '- . TEXT $)/# " ) -/ >
CREATECREATE TABLETABLE IF NOTNOT EXISTSEXISTS `autoEnum` (`id` INTEGER PRIMARYPRIMARY KEYKEY AUTOINCREMENT NOTNOT
NULLNULL, `silly` TEXT NOTNOT NULLNULL)
-/*!2#/**(* A" ) -/ .!*-0.- !0)/$*)./**)1 -//# enum class
1'0 ./* String - +- . )//$*).)&>
privateprivate StringString __Silly_enumToString(finalfinal SillySilly _value) {
ifif (_value == nullnull) {
returnreturn nullnull;
} switch (_value) {
case FirstFirst: returnreturn "First";
case SecondSecond: returnreturn "Second";
case ThirdThird: returnreturn "Third";
default: throwthrow new IllegalArgumentExceptionIllegalArgumentException("Can't convert enum to string,
unknown enum value: " + _value);
}
}
privateprivate SillySilly __Silly_stringToEnum(finalfinal StringString _value) {
ifif (_value == nullnull) {
WHATS NEW IN ROOM?
250
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
returnreturn nullnull;
} switch (_value) {
case "First": returnreturn SillySilly.FirstFirst;
case "Second": returnreturn SillySilly.SecondSecond;
case "Third": returnreturn SillySilly.ThirdThird;
default: throwthrow new IllegalArgumentExceptionIllegalArgumentException("Can't convert value to enum,
unknown value: " + _value);
}
}
**(/# )0. ./#*. !0)/$*)..$!/# 42 - 0./*( @TypeConverter</**)1 -/
*0- enum class 1'0 ./*)!-*( String - +- . )//$*)/*+0/$)/# TEXT
*'0()?
Type Converter Improvements
# @TypeConverter ))*//$*)$.0. !0'24/* " /**(/*- *")$5 /4+ . /#/
$/*/# -2$. *0')*/#)' ?*2 1 -< @TypeConverter # )!$-'4$): 3$' >
4*0#/*$(+' ( )//# !0)/$*).*)'..<2# - **(2*0'$)./)/$/ /#/
'...)  ?
**(Q?R?O$(+-*1 ./# : 3$$'$/4$)/#$.- ?
# .$(+' $(+-*1 ( )/$./#/)*24*0)#1 @TypeConverter ))*//$*).*)
) object<.1$)"**(/# ) /*$)./)/$/ /# '..?
# (*- *(+' 3$(+-*1 ( )/$.$)) 2 @ProvidedTypeConverter ))*//$*)?
#$.$.!*-. .2# - 4*0- #++4/*#1 /# @TypeConverter !0)/$*). *)
'..<0/ you 2)//* $)#-" *!- /$)"/# *% /!*-/#/'..?) +*+0'-
. $.2# - 4*02)//*0.  + ) )4$)1 -.$*)!-( 2*-&J"" -G$'/<
*$)< /?KH!*- 3(+' <+ -#+.4*0- +-*1$$)"*)1 -/ -1$)
) /*$)% //# *)1 -/ -$)/*)*% //#/2$''0. /#/*)1 -/ -!*-
@TypeConverter !0)/$*).?
*(& /#$.2*-&<4*02-$/  class /*#*0. 4*0- @TypeConverter !0)/$*)..
)*-('?*2 1 -<)*24*0)*)./-0/*-*)/#/'..*-*/# -2$.
*)8"0- $/.4*0. 8/?*0*<#*2 1 -<'.*#1 /*
@ProvidedTypeConverter .'..A' 1 '))*//$*)>
@ProvidedTypeConverter
classclass SomeValueTypeConverterSomeValueTypeConverter(privateprivate valval adapter: JsonAdapterJsonAdapter<SomeValueTypeSomeValueType>) {
@TypeConverter
funfun someValueFromJson(json: StringString) = adapter.fromJson(json)
WHATS NEW IN ROOM?
251
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
@TypeConverter
funfun someValueToJson(value: SomeValueTypeSomeValueType) = adapter.toJson(value)
}
 - <2 #1  SomeValue /4+ J)*/.#*2)# - K))$)% /  *.#$ +/ -!*-
/#//4+ ?# @TypeConverterA))*// !0)/$*)..$(+'4 ' "/ /**.#$?
@ProvidedTypeConverter / ''.**(/*''*2'.. .2$/#*0/+0'$5 -*A
-"0( )/*)./-0/*-?*2 1 -</# *./$.$)/# . *)./ +>4*0) /*.0++'4
/# $)./) *!4*0- @ProvidedTypeConverter '../*/# RoomDatabase.Builder<
1$) addTypeConverter() ''>
classclass YourRepositoryYourRepository(privateprivate valval someValueTypeAdapter: JsonAdapterJsonAdapter<SomeValueTypeSomeValueType>) {
valval db = RoomRoom.databaseBuilder(context, YourDatabaseYourDatabase::classclass.java, DB_NAMEDB_NAME)
.addTypeConverter(SomeValueTypeConverterSomeValueTypeConverter(someValueTypeAdapter))
.build()
}
# )( *!/# ))*//$*) 3+'$)./# -0' >40.$)" @ProvidedTypeConverter<
4*0#1 (*- : 3$$'$/4<0/4*0#1 /# *-- .+*)$)"- .+*).$$'$/4/*+-*1$
/# $)./) *!/# /4+ *)1 -/ -/*/# RoomDatabase?
Packaged Databases Improvements
.2 #1 . )<4*0) +&" /. 2$/#4*0-++<.*4*0#1 ./-/ -/
$(( $/ '42# )/# ++$.8-./*+ ) ?**(Q?R?O(& .*0+' *!
$(+-*1 ( )/.$)/#$.- ?
)$/$*)/*0.$)"/. .+&" ... /.H. /# #+/ -*)+&" 
/. . !*0. .*)H4*0)0. /# .( / #)$,0 /*+*+0'/ 4*0-/.
!-*(8' <0.$)" createFromFile() $)./ *! createFromAsset()?#$.<#*2 1 -<
- ,0$- . File *% /<)/#*. - (*- $90'//**( 4"$1 )/# E.*+ 
./*-" F /*)-*$PO?**(Q?R?O)*2"$1 .0. createFromInputStream()<
.*2 )" /*0-./-/ -/. !-*( InputStream<.0#.*) /#/2 ($"#/
" /!-*( openInputStream() *) ContentResolver 0.$)" Uri?
'.*</# . create...() !0)/$*).)*2#1 1-$)//#//& .)$/$*)'
RoomDatabase.PrepackagedDatabaseCallback *% /J2#*. )( (& ./#
0/#*- very "-/ !0'!*-0/*A*(+' /$*)$)(* -).@K?#$.2$'' '' 2$/#
onOpenPrepackagedDatabase() after /# /.*0- #. )*+$ )/# **(
/. $.. /0+?#/24<$!4*0) /**.*( ' )0+J ?"?< ' / 
WHATS NEW IN ROOM?
252
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
*2)'* 8' !-*( getCacheDir()K<4*0&)*2$/$..! /**.*?
@RewriteQueriesToDropUnusedColumns@RewriteQueriesToDropUnusedColumns
.$)" * $) @Query  SELECT .// ( )/$. .4?*2 1 -<4 !0'/<**($.)*/
1 -4.*+#$./$/ *0/0.$)"$/?**(2$''#++$'4- /-$ 1 all /# *'0().
1$'' /*$/!-*(4*0-/' < 1 )$!4*0- )/$/4*-*/# -*0/+0/*% /*)'40. .
! 2*!/#*. *'0().?'0.<4*02$''" /*(+$' A/$( 2-)$)"!-*(**(*0/
/# $) 9$ )4?/$.(*-  9$ )//*#1 4*0- SELECT '$.//#  3/*'0()./#/
4*0) <0//#$." /./ $*0./*& +$).4)2$/#4*0-*0/+0/*% /./-0/0- .?
$)" @RewriteQueriesToDropUnusedColumns /*4*0- @QueryA))*// !0)/$*)
/ ''.**(/*/-4/*#)' /#$.0/*(/$''4?# **(*(+$' -2$''- +' 4*0-
* 2$/#/# *'0())( ./#/- )  $)/# /#/$//0''4 3 0/ .?#/
24<4*0" //# *)1 )$ ) *! * )/#  9$ )4*!*)'4,0 -4$)"!*-/# )  
*'0().?*0)'.* @RewriteQueriesToDropUnusedColumns /*/#  )/$- @Dao
'..*-$)/ -! </*#1 $/++'4/*'' @Query !0)/$*).?
Paging 3 Support
!4*0- 0.$)""$)"RH/# )*2A'/ ./" ) -/$*)*!/#  /+&"$)"
!-( 2*-&H**(2$'')*2' /4*0. /0+!0)/$*)./#/.0++*-/$/? #
#+/ -*)+"$)" #. )0+/ /*.#*2"$)"R?
RoomDatabase.QueryCallbackRoomDatabase.QueryCallback
*-'*""$)"+0-+*. .<$/($"#/ 0. !0'/*' -)2# )**( 3 0/ ..*( ?
*-/#/<4*0)'' setQueryCallback() *)4*0- RoomDatabase.Builder 2# )4*0
- . //$)"0+**(?#$.!0)/$*)/& . RoomDatabase.QueryCallback *% /<
2#$##..$)"' onQuery() !0)/$*)H/#$.(& .$/ .4/* - +' 4
'( 3+- ..$*)$)1*-*/'$)?# - <4*0" / String *!/# /*
3 0/ ) List *!/# *% /./* *0).- +' ( )/.!*- ?A./4'
+' #*' -.$)/#/?
* - !0'</#*0"#<*0/4*0-'*""$)"<./# $)*% /.(4*)/$)//#/
.#*0' *).$ - +-$1/ ?*).$ -*)'4'*""$)"$) debug 0$'.?
RxJava 3 Support
*/ 1 -4*4- '$5 ./#$.<0/31Q?3$. )A*!A'$! >*/# -/#).*( 0"83 .<
WHATS NEW IN ROOM?
253
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!
)*) 20+/ .- +')) !*-/#/?# 31/ (#.(*1 *)/*31R<
2#$##..$($'-/*31Q?30/)*/,0$/ $ )/$'?
- 1$*0.'4<**(.0++*-/ */# -- /$1 *+/$*).H LiveData<31Q?3<
*-*0/$) .H0/)*/31R?*2<0.$)" androidx.room:room-rxjava3<4*0)
0. 31R/4+ .$)4*0-**(!0)/$*).<$!4*0.*#**. ?
WHATS NEW IN ROOM?
254
Published under the Creative Commons
Attribution-ShareAlike 4.0 International license.
Visit https://commonsware.com/licenses to learn more!