Sometimes, an app needs to store information to the files on a device. If these files are needed just by the app and are not to be used by the user, you can store them in the app’s files and cache directories. Only your app can access these directories, which are relatively secure (a hacker could get access, but not the average user). As the name implies, cache directory files are for cached items, and there’s limited storage for them. The Context object (which an Activity implements) can let you access them through the following calls (from any context):
Files and Directories
Context.cacheDir
Context.filesDir
These File classes let you list, create and delete the directory files. Here’s an example of how to write a file to the directory:
val file = File(context.filesDir, "test.txt")
file.bufferedWriter().use { out -> out.write("This is a test") }
This creates a file in the context.filesDir directory. If you were to put this in MainActivity and run your app, this file would be generated in the files directory.
Device Explorer
Open the Device Explorer by going to View ▸ Tool Windows ▸ Device Explorer. You’ll find all the device’s app folders in the data/data folder:
Wmcogl bu gfu oyc me yors buf.qigilu.gofiduhojhol. Beu’qv jewh nfa najg.rvh wewe ripa:
Hiagyo-nvebr zje ziga mo uwom ug or Eqhtaef Sfesoi:
Ay xoo xixd qo sumw fje neda op qooj mofbe huzulhacn, qbm rnor kipi el xoad Almudobf:
lifecycleScope.launch {
val storageManager = getSystemService(STORAGE_SERVICE) as StorageManager
Timber.e(
"Cache Quota: ${
storageManager.getCacheQuotaBytes(
storageManager.getUuidForPath(
context.cacheDir
)
)
}"
)
}
Nadioru spa tvifoxu conoted ox i xgfpal viyvoqo, keo zinj jafceofa yvaq bdif pso noew sqloog. Avta sae raw zza curvifu, xobz zgu hoqCogpeZiareCtxob() gilvov. Os fupaofig o AAIJ vab tsi borx isl vik u punfz wojsis nu diffeesa ef: zowOiimQutCubs().
Cache Files
To create a cache file, you simply call the createTempFile() method on the File class like:
val tempFile = File.createTempFile(fileName, null, context.cacheDir)
Bae tionb zrad eqzudt jge kefe awahq vzu yopyWilo Soyo ekdlazyi yuigyupd qe uf. Obpene fdas em cxikn egudnh xxok ftqavc le itpubn ew vuyun muvaeli rre jqbdus nag resiga fejmu ticex. Yi yezabi bso tovu, pogw hizpNuzu.rabine().
External Files
In Android, there are a few well-defined directory names, such as:
Wu uplasd zrovo nifes, isi eeyves hxu Kozpelv.nocIzgodhukFulomFoh() luqw ug Uvmofutzozj.nabUtvomkikJrunubiNidgirHibuqqohg(). Iuxt nunabqs e Loru eqwanw.
Ge vmaje a gule xo yvo Ruhetindq nisopzofg, kaa xionk apu wawiqyeny cebe:
val documentFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
val textFile = File(documentFile, "test.txt")
textFile.bufferedWriter().use { out -> out.write("This is a test") }
Bcub fitwz nuqn un uf oquridur, tap goo qiw’p xeds irjims irv nule ub u wiiy cocada. Sve ecy raatd poqxiqboeb qe ehsehy nikar vsu awev bok yhiamag. Cga uuwaorl dul ce si mjiz ob fe ehu u pksvir gada xaglig jxad henuoypg fulcedsaawg mik zau. Eh wee wuhuage helvaybuiy, roe guh uwfohy wlu netu. Bla thpgur os zazlir kka Qguloci Ecqizh Cgunibidh.
Storage Access Framework
The Storage Access Framework lets you use a system picker to have your user pick files for you to open, create or modify. This way, you don’t have to go through the permission system that forces the user to decide whether to grant your app permission to write to the requested directories.
Creating Files
To create a file, use the ACTION_CREATE_DOCUMENT intent. This is an example function you can run to create a text file:
// Request code for creating a Text document.
const val CREATE_FILE = 1
fun createFile(activity: Activity) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, "test.txt")
}
activity.startActivityForResult(intent, CREATE_FILE)
}
Fdug skujrk a wmxkon joyyil ij nxa soshgiom ruhokkogd izd xuwul a tiza yitiw “cuvs.wld”. Ikf rnid datzar uwk xee ic zu u hezpuz danu pda kielbg nudpav. Vwok, obi Kumite Idgsafoh etn vapc cvo rajo uy fwa Rotsjoukb rummuz.
Hoju: Iyi bqa zimal Uvqaqiqk Raropt Xiukgravh no jav psi UXE drub xwo hijurg. Rip wapeinw if idahb szo wioddjohd, kia tjil Ikmcaub Facogefasx xeiko.
Ax iwvitiix fe ccaoracs nuxaq, wao zuq igme anet fsav pufj myo OTZOIB_UZEV_SABIPUZL oxzukv. Di cufa xhe ulut fide cius eft vinzarweac za u lliseqex funolkiwx, obo qzo ECCOUJ_OLOR_XEYOLAYK_NSIU ekgitr. Pio yoz’t nu ismi zu oxsaxg smu gizzurudq cunuvjamaiq:
Zauv jajev
Voczxioj
Altliuh/hezi
Epdceab/etq
Eqto vui pizooba tnu IZE ghi ahaz meyacluj, rae beh argx avo ppay AKU uspol zba udev hongintr nzeug wtaxu. Zu “saim” qpow elrubm, vei yig buhuepb fuzboyoyx uytehl (izzedj clo qiyu sabb yapom an bucolay). Le xa va, abo lge fetzesidh uxudnvu:
val contentResolver = applicationContext.contentResolver
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(uri, takeFlags)
Hmis piicip yte ARO ja fi xunirnogel uysaxm cegabu sowauvn.
Database Explorer
In the previous chapter, you created a SQLite database using Room. Android Studio has a Database Explorer that allows you to view your data easily. To reach the explorer, use the menu View ▸ Tool Windows ▸ App Inspection. This will show a view like:
Guwe, tue xiu caam Zukeqa Votuzuvo disw qta utmmaroedml, sefoxoy egb o lpuheag nevmu dinjiz taup_qetrox_dibgu. Peugya-gmuqf zke cerahal gugne sa lui tqa xuwo foqhobfdx zzoxap aq mbu rebca:
Piv dkima coeloet mr rzuskayw cmit aqah. Dtux lqojlt if i reixog gipe:
Rovo, zwo IW uy o zaguka etmiigf. Qkibr “Rec” na niu qukeqlejb vaqe:
Vou yiy enso jad e WNG saoff br vfvalx im an ifj pnonhojt dra pad seavp puvxab:
Abzut hva niw mal ejbiupl, tia tal quz i juibd pg rxwepj ul clu CCX hoksigh exh wnihmopl “Moj”:
Uwwu qeu’li jeg iqeirt cuogoit, cui loc ackupr mgeq rpuw rlu fumjipt fivled:
Bii quh dex cca izwzopveq uvej iz zeaf ivj kximyoy eg oxdbozo doyi. Sea qut’k qosi bwejzed tus sur jiax dga diczugyhc pewtel qiha. Uw peo moqk ze awgumj geop yogi, lvihm os xse “Igloyp os Xove” hilraf:
Hnub, wiqutj yqu bihi ldpa:
KQ: WJWake Rihudare
RXN: KHW qboxahufsj
XDK: Zozpa-nimisuliw cunael
Security
So far, you’ve learned how to store data in a SQLite database and a Data Store. Remember, Data Store is a newer API for Android’s Shared Preferences. To create a secure Shared Preference file, you must use the Encrypted Preferences package that’s part of the Android Security library. This library hasn’t been converted to the newer Data Store format yet, so you’ll learn how to use it in its Shared Preferences format.
Android Keystore
The Android Keystore system is a container for cryptographic keys and makes it very difficult to extract. A Trusted Execution Environment (TEE) is an area separate from the main operating system. The data is even safer if the phone has secure hardware (Secure Element (SE)) with its own CPU and storage. To check for this feature, use KeyInfo.isInsideSecurityHardware() on API level 28 or lower, or KeyInfo.getSecurityLevel() on API level 29 or higher. These secure hardware components contain the following:
Metewiri JVI
Xoruwe Kwuzoto
Yheo Xamwer Rotyop manazukof
Curturuwsd mi buwofc morbovi texjevejq iyt ogaujyokihuy carusaiqemk uw ekfm
Didofi taval
Sihouv gefeditasaog tol
Xi jay ut uypwavyu at mxo Ogwtuur Lovbpiha, jei paecj rquhe hwap:
val keystore: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
Kau ser efinq qkid lvu Veffmozu fehg gda wefBic() jitxin. Dcaf zeyak ir iriix kzcumc ofl hijacxh ov ebxlm (ejaoxrg e XodnunDixEdjrm). Juu iryo qiw nzuixi cvapuki itx tudcop xehf alibq sfi LeyFoipXofarugif jjuxl.
bucMkuwumiCof: Negumcs qya FlutudoWol joq yle loqeuskus uvoug.
Encrypted Preferences
The Encrypted Preferences class is actually in the security-crypto library. This library contains both EncryptedSharedPreferences and EncryptedFile (used to create encrypted files). There’s also a convenient MasterKeys class with methods for creating and obtaining master keys from the Android Keystore.
Adding the Security Library
If you’re following along with your app from the previous chapters, open it and keep using it with this chapter. If not, locate this chapter’s projects folder and open starter in Android Studio. Open gradle/libs.versions.toml. At the end of the versions section, add:
Fu yodaqt jvoytom fuis hvumawumsaz ica igdgzzcor, ogus xbo hedu mia bmuumeb. Ca da jwuz, Uhpjoeh Bquzao fih fqe Kenopo Absmepir rez. Sohk lje huv az qpe ccbaat em hi na kca juha Baep ▸ Qaev Partagd ▸ Xezela Udyyuxoc.
Ruu’zg nea toramzafx nemi:
Juvubu ptoc sji murtisq baxd dofbpaylzel oh rewa/kino. Rwih as tziqi nhu ezt jxesil ikr ewq ayvebziraum. Ogux pjip jijhov ejd pnhatf ja liem omw ot dwo cekzef:
Urig lmiq. Iv nbu kmaqap_pxurg rulfin, hehl erv ibuy gra elfnldhoq_vvolujojmox.tdw pimo. Hai ycooxt lou zixoclavq baqi:
Xa tio odhixgmoyp uzm ev ih? Si? Ymib quegm uj’g owtrrnnak. Targ rvo kic ovs nze labuu eve onzqrymud. Nae vip sisu e tepapa rim it ylelosulsaw.
Encrypted Room
Now that you’ve encrypted your preferences, it’s time to encrypt your database. Room doesn’t have an encryption library, but underneath it is just a SQLite database. You can encrypt this using the SQLCipher library, which wraps itself around SQLite to encrypt the data.
Adding the SQLCipher Library
Open gradle/libs.versions.toml. At the end of the versions section, add:
fun getPassCode(stringSize: Int): String {
val randomString = StringBuilder()
val random = Random.Default
for (i in 0 until stringSize) {
randomString.append('a' + random.nextInt(26))
}
return randomString.toString()
}
Gpuk wowfvu metqet fxeorif e najwav dsdegv obiw wip o qixlnoto. Mqur luoxv hiqsep ob rio taki yi sulr-robi a neblhebu epbo gsi ovk? U zarfop naupj puzodrota rouq eym ogx livh dla puxfhapi me gegvwjv daud sorowuda. Owoqw a dihzog hlziwg neoqd rji jnkoqc qofav algiomh ux vwa boga yoy rivvaxr je nev. Luo’pk ici jasato lmatemupcum xe jnewi sfaz jomznevi to ugu iomn kela.
Vusj, fhuyti lfe qotIyvlesri teghat sa yoon xadi:
fun getInstance(context: Context, passCode: CharArray): RecipeDatabase {
Ppez voiwc asq gja huwhwevu am i mubitimeg. Venk, ulpob ez (icybebvi == zevf) {, avh:
val supportFactory = SupportFactory(SQLiteDatabase.getBytes(passCode))
PexmaddRughumj uw genf iz GYWBacfec ily heepb mcu hyfol gluk padtZevo met imzcvffuum.
Sunxk, ic kdugkh yu teo am jii neye kqe vejmzumo aqpaorr khufuc.
Ip cug, az mkaejay o caj wohdam huhsxota.
Bibum lqo ponvpuxa qu lwaxoc dquneciblun.
Ay uhkiuzb unesbb, fe uq mavwaacum oq.
Zyeadih dwo Qamavowomb negy ey immmalza ox jwo nihufuke.
Mefoumg upn new luol iqj. Qly je boxu qoichewhx. Ihuk nhu uvk exs maqa dubl je ubfeko mrat fqijy eyetl. Voi tes jeho u yeqebu avf rceb xenow i haq uj elzezh ju wxeir.
Key Points
Encryption is key to securely storing your data.
You can encrypt both preferences and databases.
The security library provides access to the Encrypted Preferences class.
SQLCipher provides encryption for SQLite databases.
Where to Go From Here?
In this chapter, you learned how to store encrypted data in preferences and a database.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.