david vávra: firebase + kotlin + rx + mvp

25

Upload: mdevtalk

Post on 12-Apr-2017

120 views

Category:

Mobile


1 download

TRANSCRIPT

Firebase + Kotlin + Rx + MVPDavid Vávra

@vavradav, +David Vávra, medium.com/@david.vavra

About me

Founder&CEO at

Android and backend hacker of Settle Up

Google Developer Expert for

Organizer at GDG Prague

Settle Up

Redesigned Settle Up

Architecture choices

Firebase Realtime Database

Own server Realm

Kotlin Java

MVP MVVM

Challenges & how we solved them

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

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

Q&A

Follow me:twitter.com/vavradavmedium.com/@david.vavra