david vávra: firebase + kotlin + rx + mvp
TRANSCRIPT
About me
Founder&CEO at
Android and backend hacker of Settle Up
Google Developer Expert for
Organizer at GDG Prague
How to structure data in Firebase Realtime Database?
{ "transactions": { "group_id_1": { "expense_id_1": { ... } } }, "groups": { "group_id_1": { "name": "India trip", ... } }, "permissions": { "group_id_1": { "user_id_1": { "level" : 30 } } }, "usersGroups": { "user_id_1": { "group_id_1": true, "group_id_2": true } }}
http://bit.ly/firebasestructure
How can presenter survive configuration changes
abstract class BaseFragment<P : Presenter<V>, V : MvpView> : Fragment(), LoaderManager.LoaderCallbacks<P> {
private var mPresenter: P? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
loaderManager.initLoader(PRESENTER_LOADER_ID, Bundle(), this)
}
override fun onCreateLoader(id: Int, args: Bundle): Loader<P>? {
return PresenterLoader(activity, getPresenterFactory())
}
override fun onLoadFinished(loader: Loader<P>, presenter: P) {
mPresenter = presenter
}
override fun onResume() {
mPresenter?.attachView(this as V)
}
override fun onPause() {
mPresenter?.detachView()
}
}
http://bit.ly/presenterrotation
How can presenter survive configuration changes
class PresenterLoader<T : Presenter<out MvpView>>(context: Context, val factory: PresenterFactory<T>) : Loader<T>(context) {
private var presenter: T? = null
override fun onStartLoading() {
if (presenter != null) {
deliverResult(presenter)
} else {
forceLoad()
}
}
override fun onForceLoad() {
presenter = mFactory.create()
presenter?.onCreatedByLoader()
deliverResult(presenter)
}
override fun onReset() {
presenter?.onDestroyedByLoader()
presenter = null
}
}
http://bit.ly/presenterrotation
How to coordinate multiple async calls to Firebase Realtime Database
fun observe(query: DatabaseQuery): Observable<DataSnapshot> {
return Observable.fromAsync<DataSnapshot>({
val listener = query.addValueEventListener(object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
it.onError(RuntimeException(databaseError.message))
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
it.onNext(dataSnapshot)
}
})
it.setCancellation { query.removeEventListener(listener) }
}, AsyncEmitter.BackpressureMode.BUFFER)
}
http://bit.ly/rxfirebase
How to coordinate multiple async calls to Firebase Realtime Database
object DatabaseRead {
fun getUserGroupColor(groupId: String): Observable<String> {
return ObjectQuery().apply { path = "userGroups/${Auth.getUserId()}/$groupId/color" }
.observe()
.toPrimitiveObservable(String::class.java)
}
fun getMember(groupId: String, memberId: String): Observable<Member> {
return ObjectQuery().apply { path = "members/$groupId/$memberId" }
.observe()
.toObjectObservable(Member::class.java)
}
}
How to coordinate multiple async calls to Firebase Realtime Database
private fun <T> load(observable: Observable<T>, displayToView: (T) -> Unit) {
val data = PresenterData(observable, displayToView)
data.subscription = data.observable.subscribe({
data.cachedValue = it
if (isViewAttached) {
data.displayToView(it)
}
}, {
showError(it)
})
mDataList.add(data)
}
How to coordinate multiple async calls to Firebase Realtime Database
fun loadMultiple(observables: List<Observable<out Any>>, displayToView: (Array<Any>) -> Unit) {
val data = PresenterMultipleData(observables, displayToView)
Observable.combineLatest(observables, {
it
}).subscribe({
data.cachedValue = it
if (isViewAttached) {
data.displayToView(it)
}
}, {
showError(it)
})
mMultipleDataList.add(data)
}
Concrete example - Member detail screen
{
"members": {
"group_id_2": {
"member_id_1": {
"bankAccount": "532224564654/0100",
"defaultWeight": "1",
"name": "David",
"photoUrl": "https://lh3.googleusercontent.com/...jpg"
}
}
},
"userGroups": {
"user_id_1": {
"group_id_2": {
"color": "#00BCD4",
"member": "member_id_1"
}
}
}
Concrete example - Member detail screen
class MemberDetailPresenter(val groupId: String, val memberId: String) : BasePresenter<MemberDetailMvpView>() {
override fun onCreatedByLoader() {
loadOnce(DatabaseRead.getUserGroupColor(groupId), {
getView().setGroupColor(Color.parseColor(it))
})
loadMultiple(listOf(DatabaseRead.getMember(groupId, memberId), DatabaseRead.getUserMember(groupId)) {
val member = it[0] as Member
val userMember = it[1] as String?
getView().setMember(member, member.id == userMember)
})
}
}
Concrete example - Member detail screen
class MemberDetailActivity : BaseActivity<MemberDetailPresenter, MemberDetailMvpView>(), MemberDetailMvpView {
override fun getLayoutRes(): Int {
return R.layout.activity_member_detail
}
override fun getPresenterFactory(): PresenterFactory<MemberDetailPresenter> {
return MemberDetailPresenter.Factory(getGroupId(), getMemberId())
}
override fun setMember(member: Member, thisIsMe: Boolean) {
vName.setText(member.name)
vThisIsMe.isChecked = thisIsMe
}
override fun setGroupColor(color: Int) {
vHeader.setBackgroundColor(color)
}
}
How does Firebase pricing affect monetization?
Realtime Database:
$5/stored GB monthly
Storage:
$0.026/stored GB monthly
Our monetization:Freemium
● Subscription● Or free app with video ads
http://bit.ly/firebasepricing
Windows support?
https://github.com/step-up-labs/firebase-database-dotnet
https://github.com/step-up-labs/firebase-authentication-dotnet
https://github.com/step-up-labs/firebase-storage-dotnet
TL;DR
Firebase is great
Build new apps in Kotlin
Use MVP architecture, Presenter should survive orientation change with Loader
RxJava is great for combining multiple async calls to Firebase
Think about database structure and monetization before you start building