Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Wild Storage for Android Developers

Wild Storage for Android Developers

Dinorah Tovar

January 19, 2021
Tweet

More Decks by Dinorah Tovar

Other Decks in Technology

Transcript

  1. Wild Storage for
    Android Developers
    Dinorah Tovar


    Google Developer Expert
    Platform Mobile Engineer

    @ konfío.mx
    @ddinorahtovar
    @ddinorahtovar

    View Slide

  2. Data is
    important!

    View Slide

  3. Data Layer means a lot of things
    @ddinorahtovar

    Data Layer
    Network

    Presentation Layer
    UI
    Models


    Entities
    Domain Layer
    Local
    Some Data

    View Slide

  4. Practical Network for Android Developers
    @ddinorahtovar

    View Slide

  5. Data and file storage overview
    @ddinorahtovar
    App Specific Storage
    Databases
    Shared Storage
    Preferences

    View Slide

  6. Data and file storage overview
    @ddinorahtovar
    App storage
    Databases
    Shared
    storage
    Preferences
    •App specific storage — Files
    •Shared storage — Media files, documents
    •Preferences — Primitive values
    •Databases — SQLite flavored

    View Slide

  7. Performance
    for database

    View Slide

  8. Performance can be dangerous
    @ddinorahtovar
    Request
    Response
    Click Some server
    Write to database or file

    View Slide

  9. Performance can be dangerous
    @ddinorahtovar
    Jesse Wilson

    Public Object

    View Slide

  10. Cache can be a
    lifesaver

    View Slide

  11. Cache
    @ddinorahtovar
    •Database Catching — Database provide an
    impactful factor of your app performance.
    •CDN — Global network of edge locations to
    deliver a cached copy of your APIs content
    •DNS — Every domain request made on the internet
    essentially queries DNS cache servers in order to
    resolve the IP address

    View Slide

  12. Performance in database is important
    @ddinorahtovar
    •Slow times
    •Local Cache
    •Millions of companies use SQLite
    •Scales well!
    •Android usually use SQLite

    View Slide

  13. So, databases
    @ddinorahtovar
    •Can be slow and have consume memory in all the
    incorrect ways you can imagine
    Realm
    SqlDelight
    Room


    View Slide

  14. How about SQLite?
    @ddinorahtovar
    •There’s some things about SQLite Android
    Not
    Observables
    Boilerplate for
    SQLite
    conversion
    Runtime
    crashes



    View Slide

  15. Let’s talk about the alternatives
    @ddinorahtovar
    •Almost all ORM is the “elegant” version of
    SQLite
    Coroutines/
    RxJava or
    Callback
    Support
    Simpler
    migrations
    Compile time
    verification
    or typeSafe
    API



    View Slide

  16. So, databases
    @ddinorahtovar
    •Let’s review some example (there’s so many ORM
    so let’s keep it simple)
    SqlDelight
    Room
    Realm

    View Slide

  17. So, Realm
    @ddinorahtovar
    SqlDelight
    Room
    Realm
    •Threading — Executables for
    transactions
    -
    >
    executeTransactionAsync
    •Listener — OnChangeListener
    •Has a browsers — RealmBrowser
    •Easy Encryption — Using KeyChain can
    happen really easy

    View Slide

  18. So, SQLDelight
    @ddinorahtovar
    SqlDelight
    Room
    Realm
    •TypeSafe — Typesafe Kotlin APIs from
    your SQL statements
    •Kotlin first — even for Multiplatform
    •Threading — support for Coroutines,
    RxJava and Android Paging

    View Slide

  19. So, Room
    @ddinorahtovar
    SqlDelight
    Room
    Realm
    •Compile-time — verification of SQL
    queries
    •Less boilerplate — and follows the DAO
    pattern
    •Threading — support for Coroutines,
    flows and so many other things

    View Slide

  20. See this talk if you wanna know more

    View Slide

  21. Some ✨ rules ✨
    @ddinorahtovar
    •Take what you need
    •Use foreign keys, primary keys and @Relations
    •Use @Transaction
    •Use Coroutines ✨

    View Slide

  22. Take what you need
    @ddinorahtovar
    @Query("SELECT * FROM Product")


    fun getAllProduct(): List

    View Slide

  23. Take what you need
    @ddinorahtovar
    @Query("SELECT P.id,


    P.name,


    P.price


    FROM Product P


    WHERE P.active = 1")


    fun getAllProduct(): List

    View Slide

  24. Foreign, primary keys and @Relations
    @ddinorahtovar
    @Query
    Deletions
    In cascade
    •Foreign keys are Integrity tool, but with
    Indexes, they boost performance

    View Slide

  25. Foreign, primary keys and @Relations
    @ddinorahtovar
    @Entity(foreignKeys = [


    ForeignKey(entity = PricesEntity
    :
    :
    class,


    parentColumns = arrayOf("idUPC"),


    childColumns = arrayOf("idProduct"),


    onDelete = ForeignKey.CASCADE)])


    data class ProductEntity ()

    View Slide

  26. Foreign, primary keys and @Relations
    @ddinorahtovar
    1:1 *:1 *:*
    •When the POJO is returned from a query, all of
    its relations are also fetched by Room.

    View Slide

  27. Foreign, primary keys and @Relations
    @ddinorahtovar
    @Entity


    data class PictureProduct {


    @PrimaryKey


    var idProduct: String,


    var imageUrl: String


    }
    data class AllProductsWithPictures {


    var idProduct: String,


    var name: String,


    @Relation(parentColumn = "idProduct", entityColumn = "imageUrl")


    var pictures: List


    }

    View Slide

  28. Use @Transaction
    @ddinorahtovar
    •Keep it constant
    •Specially nice for relationships
    •If asynchronous the transaction is handled when
    the query run, not when the method is called.

    View Slide

  29. Use @Transaction
    @ddinorahtovar
    @Insert


    fun insert(product: ProductEntity)


    @Delete


    fun delete(product: ProductEntity)
    @Transaction


    fun insertAndDelete(


    newProduct: ProductEntity,


    oldProduct: ProductEntity


    ) {


    insert(newProduct)


    delete(oldProduct)


    }

    View Slide

  30. Use Coroutines ✨
    @ddinorahtovar
    @Insert


    fun insertUser(user: UserEntity)
    -
    >
    Synchronous


    @Insert


    suspend fun insertUser(user: UserEntity)
    -
    >
    Suspended
    •Can be used in anything
    @Query("SELECT P.id,


    P.name,


    P.price


    FROM Product P


    WHERE P.active = 1”)


    suspend fun getAllProduct(): List


    @Transaction


    suspend fun insertAndDelete(


    newProduct: ProductEntity,


    oldProduct: ProductEntity


    ) {


    insert(newProduct)


    delete(oldProduct)


    }

    View Slide

  31. Threading and
    Concurrency

    View Slide

  32. Writing to database
    @ddinorahtovar
    Your App
    Write to
    database
    Internet
    Local cache
    Response

    View Slide

  33. Coroutines and Room ✨
    @ddinorahtovar
    •We don’t want funky performance, so we use a
    suspend function
    @Insert


    fun insertProduct(product: ProductEntity)
    -
    >
    Synchronous


    @Insert


    suspend fun insertProduct(product: ProductEntity)
    -
    >
    Suspended

    View Slide

  34. Coroutines and Room ✨
    @ddinorahtovar
    @Override


    public void insertProductSync(final ProductEntity product) {

    /
    /
    Some cool interactions


    }
    •For synchronous

    View Slide

  35. Coroutines and Room ✨
    @ddinorahtovar
    @Override


    public void insertProductSync(final ProductEntity product) {

    _
    _
    db.beginTransaction();


    try {

    _
    _
    insertionAdapterOfProduct.insert(product);

    _
    _
    db.setTransactionSuccessful();


    } finally {

    _
    _
    db.endTransaction();


    }


    }
    •For synchronous

    View Slide

  36. Coroutines and Room ✨
    @ddinorahtovar
    @Override


    public Object insertProductSuspend(


    ProductEntity product,


    final Continuation p1


    ){


    return CoroutinesRoom.execute(
    _
    _
    db, new Callable() {

    /
    /
    Some cool interactions


    }
    •For asynchronous

    View Slide

  37. Coroutines and Room ✨
    @ddinorahtovar
    @Override


    public Object insertProductSuspend(


    ProductEntity product,


    final Continuation p1


    ) {


    return CoroutinesRoom.execute(
    _
    _
    db, new Callable() {


    @Override


    public Unit call() throws Exception {

    /
    /
    Some cool interactions


    }


    }, p1);


    }

    View Slide

  38. Coroutines and Room ✨
    @ddinorahtovar
    @Override


    public Object insertProductSuspend(ProductEntity product, final Continuation p1) {


    return CoroutinesRoom.execute(
    _
    _
    db, new Callable() {


    @Override


    public Unit call() throws Exception {

    _
    _
    db.beginTransaction();


    try {


    _
    _
    insertionAdapterOfProduct.insert(product);

    _
    _
    db.setTransactionSuccessful();


    return kotlin.Unit.INSTANCE;


    } finally {

    _
    _
    db.endTransaction();


    }


    }


    }, p1);


    }

    View Slide

  39. Coroutines and Room ✨
    @ddinorahtovar
    @Override


    public Object insertProductSuspend(ProductEntity product, final Continuation p1) {


    return CoroutinesRoom.execute(
    _
    _
    db, new Callable() {


    @Override


    public Unit call() throws Exception {

    _
    _
    db.beginTransaction();


    try {


    _
    _
    insertionAdapterOfProduct.insert(product);

    _
    _
    db.setTransactionSuccessful();


    return kotlin.Unit.INSTANCE;


    } finally {

    _
    _
    db.endTransaction();


    }


    }


    }, p1);


    }

    View Slide

  40. Coroutines and Room ✨
    @ddinorahtovar
    @JvmStatic


    suspend fun execute(


    db: RoomDatabase,


    inTransaction: Boolean,


    callable: Callable


    ): R {


    if (db.isOpen
    &
    &
    db.inTransaction()) {


    return callable.call()


    }


    val context = coroutineContext[TransactionElement]
    ?
    .
    transactionDispatcher

    ?
    :
    if (inTransaction) db.transactionDispatcher else db.queryDispatcher


    return withContext(context) {


    callable.call()


    }


    }

    View Slide

  41. Coroutines and Flows
    Name Operation
    Suspendable
    Flow
    suspending functions
    asynchronous data stream that

    sequentially emits value
    Type
    Asynchronous
    Cold
    @ddinorahtovar

    View Slide

  42. Room and Flow✨
    @ddinorahtovar
    @Query("SELECT P.id, P.name, P.price FROM Product P


    WHERE P.active = 1”)


    suspend fun getAllProduct(): List
    •Normal suspend function
    •Flow suspend function
    @Query("SELECT P.id, P.name, P.price FROM Product P


    WHERE P.active = 1”)


    suspend fun getAllProduct(): Flow>
    >

    View Slide

  43. Room and Flow✨
    @ddinorahtovar
    viewModelScope.launch {


    productDao.getAllProduct().collect { products
    -
    >

    /
    /
    Do magic


    }


    }
    •ViewModel looks something like this

    View Slide

  44. Room and Flow✨
    @ddinorahtovar
    public fun flow (


    @BuilderInference


    block: suspend FlowCollector.()
    -
    >
    Unit


    ): Flow = SafeFlow(block)
    •What is inside of a Flow?

    View Slide

  45. Room and Flow ✨
    @ddinorahtovar
    private class SafeFlow(


    private val block: suspend FlowCollector.()
    -
    >
    Unit


    ) : AbstractFlow() {


    override suspend fun collectSafely(collector: FlowCollector) {


    collector.block()


    }


    }
    •What is inside of a SafeFlow?

    View Slide

  46. Room and Flow ✨
    @ddinorahtovar
    @FlowPreview


    public abstract class AbstractFlow : Flow, CancellableFlow {


    @InternalCoroutinesApi


    public final override suspend fun collect(collector: FlowCollector) {


    val safeCollector = SafeCollector(collector, coroutineContext)


    try {


    collectSafely(safeCollector)


    } finally {


    safeCollector.releaseIntercepted()


    }


    }


    public abstract suspend fun collectSafely(collector: FlowCollector)


    }
    •What is inside of a AbstractFlow?

    View Slide

  47. Room and threads
    @ddinorahtovar
    •Android’s SQLite transactions are thread
    confined
    •If a Coroutine is paused there a possibility
    that that the resumed coroutine will not be
    executed in the same thread

    View Slide

  48. Room and threads
    @ddinorahtovar
    Some Thread
    Click Suspend
    Update
    Update
    Insert
    Delete
    Insert
    Delete
    Other Thread

    View Slide

  49. Room and threads
    @ddinorahtovar

    getAllProducts()
    
 

    getAllProductsImages()

    Dispatcher.IO


    Some Thread
    getAllStores()
    Can cause a Deadlock, getAllProducts() may not be executed

    View Slide

  50. Room and threads
    @ddinorahtovar
    fun productTransaction() = GlobalScope.launch(Dispatchers.*) {


    roomDatabase.withTransaction {


    productDao.getAllProducts()


    productDao.getAllProductsImages()


    }


    }
    •No worries! There’s a way to solved it

    View Slide

  51. DataStore vs
    Shared Preferences

    View Slide

  52. Is not exactly a versus,


    they solve different
    scenarios

    View Slide

  53. Data Store and Shared Preferences
    Storage Cool facts
    Shared Preferences
    DataStore
    They are sync
    They can be encrypted
    Primitive values only
    They are async
    They are non encrypted
    Has multiple types of values
    @ddinorahtovar

    View Slide

  54. Shared Preferences
    @ddinorahtovar
    •You say encrypted?
    •And is super easy!
    implementation "androidx.security:security-crypto:1.1.0-alpha03"

    View Slide

  55. Shared Preferences
    @ddinorahtovar
    val masterKeyAvailable = MasterKey


    .Builder(application.applicationContext)


    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)


    .build()


    val secureEncryption = EncryptedSharedPreferences.create(


    This,


    SECURE_SHARED_PREFERENCES,


    masterKeyAvailable,


    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,


    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM


    )
    •How do you do it?

    View Slide

  56. Data Store ✨
    @ddinorahtovar
    •You say flow?
    •And is super easy!
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha05"

    View Slide

  57. DataStore ✨
    @ddinorahtovar
    •Preferences DataStore
    •It stores data in a key-value pairs, without any
    type safety.
    •Proto DataStore
    •It store data as custom objects. It provides
    type safety
    •Use Protobuf

    View Slide

  58. DataStore ✨
    @ddinorahtovar
    private val dataStore: DataStore = createDataStore(


    name = "dataStore"


    )
    •Create or Migrate your shared preferences
    fun getDataStore (): DataStore {


    val name = "TheNameOfMySharedPreferences"


    return this.createDataStore(


    name = name,


    migrations = listOf(


    SharedPreferencesMigration(context, name)


    )


    )


    }

    View Slide

  59. DataStore ✨
    @ddinorahtovar
    suspend fun saveSomething(id: Int) {


    dataStore.edit { preferences
    -
    >


    preferences["yourKey"] = id


    }


    }
    val something: Flow = dataStore.data


    .map { preferences
    -
    >


    preferences["yourKey"]
    ?
    :
    -1


    }

    View Slide

  60. Wild Storage for
    Android Developers
    Dinorah Tovar


    Google Developer Expert
    Platform Mobile Engineer

    @ konfío.mx
    @ddinorahtovar
    @ddinorahtovar

    View Slide