Skip to content

DroidKaigi/conference-app-2020

Folders and files

NameName
Last commit message
Last commit date
Aug 8, 2020
Apr 22, 2020
Jan 20, 2020
Mar 10, 2020
Jan 22, 2020
Mar 10, 2020
Jan 20, 2020
Mar 13, 2020
Mar 6, 2020
Feb 25, 2020
Sep 15, 2019
Feb 24, 2020
Feb 15, 2020
Mar 20, 2020
Feb 11, 2020
Feb 16, 2020
Feb 2, 2020
Jan 25, 2020
Jan 20, 2020
Jan 25, 2020
Jan 19, 2020
Feb 5, 2020
Jan 13, 2020
Jan 12, 2020
Jan 16, 2020
Feb 21, 2021
Mar 9, 2020
Jan 15, 2020
Dec 6, 2019
Jan 21, 2020
Feb 2, 2020
Aug 18, 2019
Feb 11, 2020

Repository files navigation

DroidKaigi LogoDroidKaigi 2020 official Android app

GitHub Actions

The DroidKaigi 2020 event has been canceled and development is inactive, but we'd love to hear from you if you have any improvements.

You can install the production app via Get it on Google Play.

You can install the production app via Get it on Google Play

And also, you can try the binary under development built on master branch through Try it on your device via DeployGate

   Try it on your device via DeployGate

Features

top drawer

Contributing

We always welcome any and all contributions! See CONTRIBUTING.md for more information

For Japanese speakers, please see CONTRIBUTING.ja.md

Requirements

Android Studio 3.6 and higher. You can download it from this page.

Development Environment

Multi module project

We separate the modules for each feature. We use the Dynamic feature modules for additional features.

Kotlin Multiplatform Project

This app uses Kotlin Multiplatoform to share API and model classes (such as Session and Room classes) between Android and iOS.

Architecture

This app uses an AndroidJetpack(AAC) based architecture using AAC(LiveData, ViewModel, Room), Kotlin, Kotlin Coroutines Flow, DataBinding, Dagger, Firebase.

It is designed to be a unidirectional data flow within the ViewModel.

Fragment

Just observe() the LiveData<UiModel> of the ViewModel.

@Inject lateinit var sessionDetailViewModelFactory: SessionDetailViewModel.Factory
private val sessionDetailViewModel by assistedViewModels {
    sessionDetailViewModelFactory.create(navArgs.sessionId)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    ...
    sessionDetailViewModel.uiModel
        .observe(viewLifecycleOwner) { uiModel: SessionDetailViewModel.UiModel ->
            ...
            progressTimeLatch.loading = uiModel.isLoading
            uiModel.session
                ?.let { session -> setupSessionViews(session) }
        }
    }

ViewModel

The LiveData Kotlin Coroutines builder runs when LiveData becomes active.
And observe the data of the Coroutiens Flow of the repository.

The LiveData becomes LoadState.Loading before the Coroutiens Flow is executed by Flow.toLoadingState(), and becomes LoadState.Loaded when finished.

class SessionsViewModel @Inject constructor(
    val sessionRepository: SessionRepository
) : ViewModel() {

...
    private val sessionLoadState: LiveData<LoadState<SessionContents>> = liveData {
        emitSource(
            sessionRepository.sessionContents()
                .toLoadingState()
                .asLiveData()
        )
        sessionRepository.refresh()
    }

Construct UiModel LiveData from some such LiveData.
The combine method works like RxJava's combineLatest.
You can make the loading state of the screen from multiple LiveData states like sessionLoadState.isLoading || favoriteState.isLoading.

class SessionDetailViewModel @AssistedInject constructor(
    @Assisted private val sessionId: SessionId,
    private val sessionRepository: SessionRepository
) : ViewModel() {
...
    val uiModel: LiveData<UiModel> = combine(
        initialValue = UiModel.EMPTY,
        liveData1 = sessionLoadStateLiveData,
        liveData2 = favoriteLoadingStateLiveData
    ) { current: UiModel,
        sessionLoadState: LoadState<Session>,
        favoriteState: LoadingState ->
        // You can create loading state by multiple LiveData
        val isLoading = sessionLoadState.isLoading || favoriteState.isLoading
        UiModel(
            isLoading = isLoading,
            error = sessionLoadState
                .getErrorIfExists()
                .toAppError()
                ?: favoriteState
                    .getErrorIfExists()
                    .toAppError()
            ,
            session = sessionLoadState.value
        )
    }

Run Coroutines with viewModelScope when data changes, such as adding a session to Favorites.
Because we do not want to end the process of adding a session to favorites with the back button, we use WorkManager to do the processing.

class SessionDetailViewModel @AssistedInject constructor(
    @Assisted private val sessionId: SessionId,
    private val sessionRepository: SessionRepository
) : ViewModel() {
..
    private var favoriteLoadingStateLiveData: MutableLiveData<LoadingState> = MutableLiveData(LoadingState.Loaded)
...
    fun favorite(session: Session) {
        viewModelScope.launch {
            favoriteLoadingStateLiveData.value = LoadingState.Loading
            try {
                sessionRepository.toggleFavoriteWithWorker(session.id)
                favoriteLoadingStateLiveData.value = LoadingState.Loaded
            } catch (e: Exception) {
                favoriteLoadingStateLiveData.value = LoadingState.Error(e)
            }
        }
    }

Design

Thanks

Thank you for contributing!

Credit

This project uses some modern Android libraries and source codes.

Android

iOS