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

Firebase & Jetpack: fit like a glove (360|AnDev, Denver)

Firebase & Jetpack: fit like a glove (360|AnDev, Denver)

Doug Stevenson

July 19, 2018
Tweet

More Decks by Doug Stevenson

Other Decks in Technology

Transcript

  1. Firebase & Jetpack:

    fit like a glove
    Doug Stevenson
    @CodingDoug
    Get the code:
    Get the app:
    bit.ly/2NtAuQP
    bit.ly/2NtGedo

    View full-size slide

  2. @CodingDoug
    @CodingDoug

    View full-size slide

  3. @CodingDoug
    @CodingDoug
    Cloud hosted, realtime, NoSQL

    View full-size slide

  4. @CodingDoug
    @CodingDoug

    View full-size slide

  5. @CodingDoug
    A simple example … or is it?
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("coll").document("id")
    ref.get()
    .addOnSuccessListener { snapshot ->
    // We have data! Now what?
    }
    .addOnFailureListener { exception ->
    // Oh, snap
    }
    How should
    I do async
    programming?
    Do I directly
    update my
    views here?
    What manages
    this singleton?

    View full-size slide

  6. @CodingDoug


    Your
    Arch
    WW D?

    View full-size slide

  7. @CodingDoug
    Data Binding
    Lifecycles
    LiveData
    Navigation
    Paging
    Room
    ViewModel
    WorkManager

    View full-size slide

  8. @CodingDoug
    LiveData
    ViewModel
    Notify views when underlying database changes
    Manage UI-related data in a lifecycle-conscious way

    View full-size slide

  9. @CodingDoug
    @CodingDoug

    View full-size slide

  10. @CodingDoug
    @CodingDoug

    View full-size slide

  11. @CodingDoug
    @CodingDoug

    View full-size slide

  12. @CodingDoug
    @CodingDoug

    View full-size slide

  13. @CodingDoug
    Handle stock price changes from a document in Firestore

    View full-size slide

  14. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()

    View full-size slide

  15. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("stocks-live").document("HSTK")

    View full-size slide

  16. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("stocks-live").document("HSTK")
    ref.addSnapshotListener { snapshot, exception ->
    if (snapshot != null) {
    }
    else if (exception != null) {
    }
    }

    View full-size slide

  17. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("stocks-live").document("HSTK")
    ref.addSnapshotListener { snapshot, exception ->
    if (snapshot != null) {
    val model = StockPrice(
    ticker = snapshot.id,
    price = snapshot.getDouble("price")!!.toFloat()
    )
    }
    else if (exception != null) {
    }
    }

    View full-size slide

  18. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("stocks-live").document("HSTK")
    ref.addSnapshotListener { snapshot, exception ->
    if (snapshot != null) {
    val model = StockPrice(
    ticker = snapshot.id,
    price = snapshot.getDouble("price")!!.toFloat()
    )
    }
    else if (exception != null) {
    TODO("This is just a simple code sample, ain't got time for this.")
    }
    }

    View full-size slide

  19. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("stocks-live").document("HSTK")
    ref.addSnapshotListener { snapshot, exception ->
    if (snapshot != null) {
    val model = StockPrice(
    ticker = snapshot.id,
    price = snapshot.getDouble("price")!!.toFloat()
    )
    someTextView.text = model.ticker
    }
    else if (exception != null) {
    TODO("This is just a simple code sample, ain't got time for this.")
    }
    }

    View full-size slide

  20. @CodingDoug
    Handle stock price changes from a document in Firestore
    val firestore = FirebaseFirestore.getInstance()
    val ref = firestore.collection("stocks-live").document("HSTK")
    ref.addSnapshotListener { snapshot, exception ->
    if (snapshot != null) {
    val model = StockPrice(
    ticker = snapshot.id,
    price = snapshot.getDouble("price")!!.toFloat()
    )
    someTextView.text = model.ticker
    throw PoorArchitectureException("let's rethink mixing data store and views”)
    }
    else if (exception != null) {
    TODO("This is just a simple code sample, ain't got time for this.")
    }
    }

    View full-size slide

  21. @CodingDoug

    LiveData

    View full-size slide

  22. @CodingDoug
    @CodingDoug
    LiveData
    Observable
    Code can register observers to
    receive updates.
    Genericized
    Subclasses must specify a type
    of object containing updates.
    Lifecycle-aware
    Handles component start, stop,
    and destroy states automatically.

    View full-size slide

  23. @CodingDoug
    Keep views up to date with LiveData
    class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.stuff)
    val tickerTextView = findViewById(R.id.tv_ticker)
    val priceTextView = findViewById(R.id.tv_price)
    }
    }

    View full-size slide

  24. @CodingDoug
    Keep views up to date with LiveData
    class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.stuff)
    val tickerTextView = findViewById(R.id.tv_ticker)
    val priceTextView = findViewById(R.id.tv_price)
    val liveData: LiveData = getLiveDataForMyTicker("HSTK")
    }
    }

    View full-size slide

  25. @CodingDoug
    Keep views up to date with LiveData
    class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.stuff)
    val tickerTextView = findViewById(R.id.tv_ticker)
    val priceTextView = findViewById(R.id.tv_price)
    val liveData: LiveData = getLiveDataForMyTicker("HSTK")
    liveData.observe(this@MyActivity, Observer { stockPrice ->
    })
    }
    }

    View full-size slide

  26. @CodingDoug
    Keep views up to date with LiveData
    class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.stuff)
    val tickerTextView = findViewById(R.id.tv_ticker)
    val priceTextView = findViewById(R.id.tv_price)
    val liveData: LiveData = getLiveDataForMyTicker("HSTK")
    liveData.observe(this@MyActivity, Observer { stockPrice ->
    if (stockPrice != null) {
    tickerTextView.text = stockPrice.ticker
    priceTextView.text = stockPrice.price.toString()
    }
    })
    }
    }

    View full-size slide

  27. @CodingDoug
    LiveData is aware of the Android Activity lifecycle

    View full-size slide

  28. @CodingDoug
    LiveData is aware of the Android Activity lifecycle
    override fun onStop() {
    super.onStop()
    // LivaData becomes inactive, doesn't notify observers
    }

    View full-size slide

  29. @CodingDoug
    LiveData is aware of the Android Activity lifecycle
    override fun onStop() {
    super.onStop()
    // LivaData becomes inactive, doesn't notify observers
    }
    override fun onStart() {
    super.onStart()
    // LivaData becomes active again, notifies observers with latest data
    }

    View full-size slide

  30. @CodingDoug
    LiveData is aware of the Android Activity lifecycle
    override fun onStop() {
    super.onStop()
    // LivaData becomes inactive, doesn't notify observers
    }
    override fun onStart() {
    super.onStart()
    // LivaData becomes active again, notifies observers with latest data
    }
    override fun onDestroy() {
    super.onDestroy()
    // LiveData removes all activity-scoped observers - no leaks!
    }

    View full-size slide

  31. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData()
    {
    }

    View full-size slide

  32. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData()
    : LiveData() {
    }

    View full-size slide

  33. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData(private val documentReference: DocumentReference)
    : LiveData() {
    }

    View full-size slide

  34. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData(private val documentReference: DocumentReference)
    : LiveData() {
    override fun onActive() {
    // # observers 0 -> 1 woo-hoo! someone cares!
    }
    }

    View full-size slide

  35. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData(private val documentReference: DocumentReference)
    : LiveData() {
    private var listenerRegistration: ListenerRegistration? = null
    override fun onActive() {
    // # observers 0 -> 1 woo-hoo! someone cares!
    listenerRegistration = documentReference.addSnapshotListener(....)
    }
    }

    View full-size slide

  36. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData(private val documentReference: DocumentReference)
    : LiveData() {
    private var listenerRegistration: ListenerRegistration? = null
    override fun onActive() {
    // # observers 0 -> 1 woo-hoo! someone cares!
    listenerRegistration = documentReference.addSnapshotListener(....)
    }
    override fun onInactive() {
    // # observers 1 -> 0 awww, everyone left...
    }
    }

    View full-size slide

  37. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData(private val documentReference: DocumentReference)
    : LiveData() {
    private var listenerRegistration: ListenerRegistration? = null
    override fun onActive() {
    // # observers 0 -> 1 woo-hoo! someone cares!
    listenerRegistration = documentReference.addSnapshotListener(....)
    }
    override fun onInactive() {
    // # observers 1 -> 0 awww, everyone left...
    listenerRegistration!!.remove()
    }
    }

    View full-size slide

  38. @CodingDoug
    Implement LiveData with Firestore document updates
    class StockPriceLiveData(private val documentReference: DocumentReference)
    : LiveData(), EventListener {
    private var listenerRegistration: ListenerRegistration? = null
    override fun onActive() {
    // # observers 0 -> 1 woo-hoo! someone cares!
    listenerRegistration = documentReference.addSnapshotListener(this)
    }
    override fun onInactive() {
    // # observers 1 -> 0 awww, everyone left...
    listenerRegistration!!.remove()
    }
    override fun onEvent(snap: DocumentSnapshot?, e: FirebaseFirestoreException?) {}
    }

    View full-size slide

  39. @CodingDoug
    Implement LiveData with Firestore document updates
    override fun onEvent(snap: DocumentSnapshot?, e: FirebaseFirestoreException?) {
    }

    View full-size slide

  40. @CodingDoug
    Implement LiveData with Firestore document updates
    override fun onEvent(snap: DocumentSnapshot?, e: FirebaseFirestoreException?) {
    if (snap != null && snap.exists()) {
    val model = StockPrice(
    snap.id,
    snap.getDouble("price")!!.toFloat()
    )
    }
    }

    View full-size slide

  41. @CodingDoug
    Implement LiveData with Firestore document updates
    override fun onEvent(snap: DocumentSnapshot?, e: FirebaseFirestoreException?) {
    if (snap != null && snap.exists()) {
    val model = StockPrice(
    snap.id,
    snap.getDouble("price")!!.toFloat()
    )
    // Here you go, all my admiring observers! Go update your UI!
    setValue(model)
    }
    }

    View full-size slide

  42. @CodingDoug
    Implement LiveData with Firestore document updates
    override fun onEvent(snap: DocumentSnapshot?, e: FirebaseFirestoreException?) {
    if (snap != null && snap.exists()) {
    val model = StockPrice(
    snap.id,
    snap.getDouble("price")!!.toFloat()
    )
    // Here you go, all my admiring observers! Go update your UI!
    setValue(model)
    }
    else if (e != null) {
    TODO("You should handle errors. Do as I say, not as I do.")
    }
    }

    View full-size slide

  43. @CodingDoug
    ● Who creates the instance of LiveData?
    ○ StockPriceLiveData constructor exposes Firestore implementation details.
    ○ Remember: views should know nothing of data store.
    ● LiveData loses its data on configuration change.
    ○ Would rather have immediate LiveData results after configuration change.
    Two problems to resolve

    View full-size slide

  44. @CodingDoug
    @CodingDoug
    ViewModel
    Survives configuration changes
    Same ViewModel instance
    appears in reconfigured activities
    Shared & Managed
    System manages the scope of
    instances, may be shared
    Lifecycle-aware
    Automatically cleaned up on final
    destroy

    View full-size slide

  45. @CodingDoug
    ViewModel

    View full-size slide

  46. @CodingDoug
    Implement a ViewModel that yields a LiveData
    class StocksViewModel : ViewModel() {
    }

    View full-size slide

  47. @CodingDoug
    Implement a ViewModel that yields a LiveData
    class StocksViewModel : ViewModel() {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    }
    }

    View full-size slide

  48. @CodingDoug
    Implement a ViewModel that yields a LiveData
    class StocksViewModel : ViewModel() {
    // Find a repository object using dependency injection
    private val repository: StockDataRepository = ...
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    }
    }

    View full-size slide

  49. @CodingDoug
    Implement a ViewModel that yields a LiveData
    class StocksViewModel : ViewModel() {
    // Find a repository object using dependency injection
    private val repository: StockDataRepository = ...
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    }
    }
    // StockDataRepository knows where the data actually comes from
    interface StockDataRepository {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData
    }

    View full-size slide

  50. @CodingDoug
    Implement a ViewModel that yields a LiveData
    class StocksViewModel : ViewModel() {
    // Find a repository object using dependency injection
    private val repository: StockDataRepository = ...
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    return repository.getStockPriceLiveData(ticker)
    }
    }
    // StockDataRepository knows where the data actually comes from
    interface StockDataRepository {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData
    }

    View full-size slide

  51. @CodingDoug
    Implement a repository backed by Firestore
    interface StockDataRepository {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData
    }

    View full-size slide

  52. @CodingDoug
    Implement a repository backed by Firestore
    interface StockDataRepository {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData
    }
    class FirestoreStockDataRepository : StockDataRepository {
    override fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    }
    }

    View full-size slide

  53. @CodingDoug
    Implement a repository backed by Firestore
    interface StockDataRepository {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData
    }
    class FirestoreStockDataRepository : StockDataRepository {
    // Should use DI for this too
    private val firestore = FirebaseFirestore.getInstance()
    override fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    }
    }

    View full-size slide

  54. @CodingDoug
    Implement a repository backed by Firestore
    interface StockDataRepository {
    fun getStockPriceLiveData(ticker: String): StockPriceLiveData
    }
    class FirestoreStockDataRepository : StockDataRepository {
    // Should use DI for this too
    private val firestore = FirebaseFirestore.getInstance()
    override fun getStockPriceLiveData(ticker: String): StockPriceLiveData {
    val ref = firestore.collection("stocks-live").document(ticker)
    return StockPriceLiveData(ref)
    }
    }

    View full-size slide

  55. @CodingDoug
    LiveData &

    ViewModel &

    Repository &

    Firebase

    View full-size slide

  56. @CodingDoug
    Use ViewModel and LiveData in an activity
    class MyActivity : AppCompatActivity() {
    private lateinit var tickerTextView: TextView
    private lateinit var priceTextView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Redacted: inflate and initialize views...
    }
    }

    View full-size slide

  57. @CodingDoug
    Use ViewModel and LiveData in an activity
    class MyActivity : AppCompatActivity() {
    private lateinit var tickerTextView: TextView
    private lateinit var priceTextView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Inflate and initialize views...
    val viewModel = ViewModelProviders.of(this).get(StocksViewModel::class.java)
    }
    }

    View full-size slide

  58. @CodingDoug
    Use ViewModel and LiveData in an activity
    class MyActivity : AppCompatActivity() {
    private lateinit var tickerTextView: TextView
    private lateinit var priceTextView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Inflate and initialize views...
    val viewModel = ViewModelProviders.of(this).get(StocksViewModel::class.java)
    val liveData = viewModel.getStockPriceLiveData("HSTK")
    }
    }

    View full-size slide

  59. @CodingDoug
    Use ViewModel and LiveData in an activity
    class MyActivity : AppCompatActivity() {
    private lateinit var tickerTextView: TextView
    private lateinit var priceTextView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Inflate and initialize views...
    val viewModel = ViewModelProviders.of(this).get(StocksViewModel::class.java)
    val liveData = viewModel.getStockPriceLiveData("HSTK")
    liveData.observe(this, Observer { stockPrice ->
    })
    }
    }

    View full-size slide

  60. @CodingDoug
    Use ViewModel and LiveData in an activity
    class MyActivity : AppCompatActivity() {
    private lateinit var tickerTextView: TextView
    private lateinit var priceTextView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Inflate and initialize views...
    val viewModel = ViewModelProviders.of(this).get(StocksViewModel::class.java)
    val liveData = viewModel.getStockPriceLiveData("HSTK")
    liveData.observe(this, Observer { stockPrice ->
    if (stockPrice != null) {
    tickerTextView.text = stockPrice.ticker
    priceTextView.text = stockPrice.price.toString()
    }
    })
    }
    }

    View full-size slide

  61. @CodingDoug
    ● LiveData doesn’t propagate errors
    ● Errors need to bubble up to the UI

    class DataOrException(val data: T?, val exception: E?)
    typealias StockPriceOrException = DataOrException
    LiveData => LiveData
    What about errors?

    View full-size slide

  62. @CodingDoug
    ● https://github.com/CodingDoug/firebase-jetpack
    ● Sync individual documents/nodes, and queries
    ○ Query → RecyclerView support is inefficient and still needs work
    ● Implementations for both Realtime Database and Firestore
    ● Background sync with WorkManager
    ● Backend implemented with TypeScript for node and Cloud Functions
    ● Stay tuned for more!
    Lots more in the repo

    View full-size slide

  63. Thank you!
    @CodingDoug
    firebase.google.com
    Get the code:
    Get the app:
    bit.ly/2NtAuQP
    bit.ly/2NtGedo

    View full-size slide