•  


Using Android Architecture Components with Firebase Realtime Database (Part 3)

Using Android Architecture Components with Firebase Realtime Database (Part 3)

Hey, welcome to part 3 in this series about using lifecycle-aware Android Architecture Components with Firebase Realtime Database. In part 1 , we started with a simple Activity that uses database listeners to keep its UI fresh as data changes in the database. We converted that to use LiveData and ViewModel to remove the boilerplate of dealing with the listeners during the Activity lifecycle. Then, in part 2 , we completely refactored away all mention of Realtime Database from the Activity, and implemented a performance enhancement. This optimization uses MediatorLiveData and some threading, for the case where data manipulation might be too expensive operation to perform on the main thread.

There’s one more optimization that can be applied in the code. It could have a large impact on performance, depending on how much data your database listeners are receiving. It has to do with how our FirebaseQueryLiveData implementation deals with the database listener during its onActive() and onInactive() methods. Here it is again:

public
 class
 FirebaseQueryLiveData
 extends
 LiveData
<
DataSnapshot
>
 {

    private
 static
 final
 String
 LOG_TAG
 =
 "FirebaseQueryLiveData"
;


    private
 final
 Query
 query
;

    private
 final
 MyValueEventListener
 listener 
=
 new
 MyValueEventListener
(
)
;


    public
 FirebaseQueryLiveData
(
Query
 query
)
 {

        this
.
query 
=
 query
;

    }


    public
 FirebaseQueryLiveData
(
DatabaseReference
 ref
)
 {

        this
.
query 
=
 ref
;

    }


    @Override

    protected
 void
 onActive
(
)
 {

        query
.
addValueEventListener
(
listener
)
;

    }


    @Override

    protected
 void
 onInactive
(
)
 {

        query
.
removeEventListener
(
listener
)
;

    }


    private
 class
 MyValueEventListener
 implements
 ValueEventListener
 {

        @Override

        public
 void
 onDataChange
(
DataSnapshot
 dataSnapshot
)
 {

            setValue
(
dataSnapshot
)
;

        }


        @Override

        public
 void
 onCancelled
(
DatabaseError
 databaseError
)
 {

            Log
.
e
(
LOG_TAG
,
 "Can't listen to query "
 +
 query
,
 databaseError
.
toException
(
)
)
;

        }

    }

}

The key detail to note here is that a database listener is added during onActive() and removed during onInactive() . The Activity that makes use of FirebaseQueryLiveData executes this code during its onCreate() :

HotStockViewModel
 viewModel 
=
 ViewModelProviders
.
of
(
this
)
.
get
(
HotStockViewModel
.
class
)
;

LiveData
<
DataSnapshot
>
 liveData 
=
 viewModel
.
getDataSnapshotLiveData
(
)
;


liveData
.
observe
(
this
,
 new
 Observer
<
DataSnapshot
>
(
)
 {

    @Override

    public
 void
 onChanged
(
@Nullable
 DataSnapshot
 dataSnapshot
)
 {

        if
 (
dataSnapshot 
!=
 null
)
 {

            // update the UI here with values in the snapshot

        }

    }

}
)
;

The observer here follows the lifecycle of the Activity . LiveData considers an observer to be in an active state if its lifecycle is in the STARTED or RESUMED state. The observer transitions to an inactive state if its lifecycle is in the DESTROYED state. The onActive() method is called when the LiveData object has at least one active observer, and the onInactive() method is called when the LiveData object doesn’t have any active observers. So, what happens here when the Activity is launched, then goes through a configuration change (such as a device reorientation)? The sequence of events (when there is a single UI controller observing a FirebaseQueryLiveData ) is like this:

  1. Activity is started.
  2. LiveData is observed and becomes active, invoking its onActive() method.
  3. Database listener is added.
  4. Data is received; UI is updated.
  5. Device is reoriented, Activity is destroyed
  6. LiveData is unobserved and becomes inactive, invoking its onInactive() method.
  7. Database listener is removed.
  8. New Activity is started to take the place of the original.
  9. LiveData is observed and becomes active again, invoking its onActive() method.
  10. Database listener is added.
  11. Data is received; UI is updated.

I’ve bolded the steps that deal with the database listener. You can see here the Activity configuration change caused the listener to be removed and added again. These steps spell out the cost of a second round trip to and from the Realtime Database server to pull down all the data for the second query, even if the results didn’t change. I definitely don’t want that to happen, because LiveData already retains the latest snapshot of data! This extra query is wasteful, both of the end user’s data plan, and and counts against the quota or the bill of your Firebase project.

How do we prevent this unnecessary query?

There’s no easy way to change the way that the LiveData object becomes active or inactive. But we can make some guesses about how quickly that state could change when the Activity is going through a configuration change. Let’s make the assumption that a configuration change will take no more than two seconds (it’s normally much faster). With that, one strategy could add a delay before FirebaseQueryLiveData removes the database listener after the call to onInactive() . Here’s an implementation of that, with a few changes and additions to FirebaseQueryLiveData :

private
 boolean
 listenerRemovePending 
=
 false
;

private
 final
 Handler
 handler 
=
 new
 Handler
(
)
;

private
 final
 Runnable
 removeListener 
=
 new
 Runnable
(
)
 {

    @Override

    public
 void
 run
(
)
 {

        query
.
removeEventListener
(
listener
)
;

        listenerRemovePending 
=
 false
;

    }

}
;


@Override

protected
 void
 onActive
(
)
 {

    if
 (
listenerRemovePending
)
 {

        handler
.
removeCallbacks
(
removeListener
)
;

    }

    else
 {

        query
.
addValueEventListener
(
listener
)
;

    }

    listenerRemovePending 
=
 false
;

}


@Override

protected
 void
 onInactive
(
)
 {

    // Listener removal is schedule on a two second delay

    handler
.
postDelayed
(
removeListener
,
 2000
)
;

    listenerRemovePending 
=
 true
;

}

Here, I’m using a Handler to schedule the removal of the database listener (by posting a Runnable callback that performs the removal) on a two second delay after the LiveData becomes inactive. If it becomes active again before those two seconds have elapsed, we simply eliminate that scheduled work from the Handler , and allow the listener to keep listening. This is great for both our users and our wallets!

Are you using lifecycle-aware Android Architecture components along with Firebase in your app? How’s it going? Join the discussion of all things Firebase on our Google group firebase-talk .

- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본