/conference-app-2018

The Official Conference App for DroidKaigi 2018 Tokyo

Primary LanguageKotlin

DroidKaigi 2018 official Android app

CircleCIWaffle.io - Columns and their card count

DroidKaigi 2018 is a conference tailored for developers on 8th and 9th February 2018.

Try it on your device via DeployGate

Features

TBD

Contributing

We use waffle.io to manage tasks. If you'd like to contribute to the project but are not sure where to start off, please look for issues labelled welcome contribute.

We've designated these issues as good candidates for easy contribution. You can always fork the repository and send a pull request (on a branch other than master).

We do accept suggestions for translations at res/values-**/strings.xml.

Development Environment

Kotlin

This app is full Kotlin!

RxJava2 & LiveData

Converting RxJava2's publisher to AAC LiveData with LiveDataReactiveStreams.

AllSessionsViewModel.kt

repository.sessions
    .toResult(schedulerProvider)
    .toLiveData()

LiveDataReactiveStreamsExt.kt

fun <T> Publisher<T>.toLiveData() = LiveDataReactiveStreams.fromPublisher(this)

Groupie

By using Groupie you can simplify the implementation around RecyclerView.

data class SpeakerItem(
        val speaker: Speaker
) : BindableItem<ItemSpeakerBinding>(speaker.id.hashCode().toLong()) {

    override fun bind(viewBinding: ItemSpeakerBinding, position: Int) {
        viewBinding.speaker = speaker
    }

    override fun getLayout(): Int = R.layout.item_speaker
}

Architecture

This app uses an Android Architecture Components(AAC) based architecture using AAC(LiveData, ViewModel, Room), Kotlin, RxJava, DataBinding, dependency injection, Firebase.

Fragment -> ViewModel

Use LifecycleObserver for telling lifecycle to ViewModel.

SessionsFragment.kt

class SessionsFragment : Fragment(), Injectable {

    private lateinit var sessionsViewModel: SessionsViewModel

...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
        lifecycle.addObserver(sessionsViewModel)
        ...

SessionsViewModel.kt

class SessionsViewModel @Inject constructor(
        private val repository: SessionRepository,
        private val schedulerProvider: SchedulerProvider
) : ViewModel(), LifecycleObserver {
  ...
    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate() {
    ...
}

ViewModel -> Repository

Use RxJava2(RxKotlin) and ViewModel#onCleared() for preventing leaking.

SessionsViewModel.kt

    private val compositeDisposable: CompositeDisposable = CompositeDisposable()

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate() {
        repository
                .refreshSessions()
                .subscribeBy(onError = defaultErrorHandler())
                .addTo(compositeDisposable)
    }

    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear()
    }

Repository -> API, Repository -> DB

Use Retrofit and save to the Architecture Component Room.

SessionDataRepository.kt

    override fun refreshSessions(): Completable {
        return api.getSessions()
                .doOnSuccess { response ->
                    sessionDatabase.save(response)
                }
                .subscribeOn(schedulerProvider.computation())
                .toCompletable()
    }

DB -> Repository

Use Room with RxJava2 Flowable Support. And SessionDataRepository holds Flowable property.

SessionDao.kt

    @Query("SELECT room_id, room_name FROM session GROUP BY room_id ORDER BY room_id")
    abstract fun getAllRoom(): Flowable<List<RoomEntity>>

SessionDataRepository.kt

class SessionDataRepository @Inject constructor(
        private val sessionDatabase: SessionDatabase,
...
) : SessionRepository {

    override val rooms: Flowable<List<Room>> =
            sessionDatabase.getAllRoom().toRooms()

Repository -> ViewModel

We create LiveData from a ReactiveStreams publisher with LiveDataReactiveStreams

SessionsViewModel.kt

    val rooms: LiveData<Result<List<Room>>> by lazy {
        repository.rooms
                .toResult(schedulerProvider)
                .toLiveData()
    }

LiveDataReactiveStreamsExt.kt

fun <T> Publisher<T>.toLiveData() = LiveDataReactiveStreams.fromPublisher(this) as LiveData<T>

And using Result class for error handling with Kotlin extension.

fun <T> Flowable<T>.toResult(schedulerProvider: SchedulerProvider): Flowable<Result<T>> =
        compose { item ->
            item
                    .map { Result.success(it) }
                    .onErrorReturn { e -> Result.failure(e.message ?: "unknown", e) }
                    .observeOn(schedulerProvider.ui())
                    .startWith(Result.inProgress())
        }
sealed class Result<T>(val inProgress: Boolean) {
    class InProgress<T> : Result<T>(true)
    data class Success<T>(var data: T) : Result<T>(false)
    data class Failure<T>(val errorMessage: String?, val e: Throwable) : Result<T>(false)

ViewModel -> Fragment

Fragment observe ViewModel's LiveData. We can use the result with Kotlin when expression. In is Result.Success block, you can access data with result.data by Kotlin Smart cast.

SessionsFragment.kt

        sessionsViewModel.rooms.observe(this, { result ->
            when (result) {
                is Result.InProgress -> {
                    binding.progress.show()
                }
                is Result.Success -> {
                    binding.progress.hide()
                    sessionsViewPagerAdapter.setRooms(result.data)
                }
                is Result.Failure -> {
                    Timber.e(result.e)
                    binding.progress.hide()
                }
            }
        })

Thanks

Thank you for contributing!

Credit

This project uses some modern Android libraries and source codes.

License

Copyright 2018 DroidKaigi

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.