Permalink
Browse files

Add FirebaseAuth plumbing - show current user icon.

Major changes: I had to introduce an `ObservableUseCase` class to handle
situations where a producer will continue to emit events to the
LiveData. The overall design works the same as UseCase.

Added Repository and data classes to interact with firebase.

Added UI for login/logout.

BUG: 74200462
Change-Id: If51891513163614b591a57c8b070ff21c5735841
  • Loading branch information...
objcode committed Mar 7, 2018
1 parent 4f911a2 commit efd6167da84c184d7ba707b29a39b1e68b9bbf11
Showing with 1,461 additions and 100 deletions.
  1. +6 −0 build.gradle
  2. +7 −0 mobile/build.gradle
  3. +7 −0 mobile/proguard-rules.pro
  4. +14 −10 mobile/src/main/java/com/google/samples/apps/iosched/di/AppComponent.kt
  5. +104 −0 mobile/src/main/java/com/google/samples/apps/iosched/ui/login/LoginViewModelPlugin.kt
  6. +31 −0 mobile/src/main/java/com/google/samples/apps/iosched/ui/login/LoginViewModelPluginModule.kt
  7. +38 −6 mobile/src/main/java/com/google/samples/apps/iosched/ui/schedule/ScheduleBindingAdapters.kt
  8. +25 −16 mobile/src/main/java/com/google/samples/apps/iosched/ui/schedule/ScheduleFragment.kt
  9. +13 −23 mobile/src/main/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModel.kt
  10. +30 −0 mobile/src/main/java/com/google/samples/apps/iosched/util/CircularOutlineProvider.kt
  11. +17 −13 mobile/src/main/java/com/google/samples/apps/iosched/util/login/Login.kt
  12. +1 −1 mobile/src/main/java/com/google/samples/apps/iosched/util/login/LoginResult.kt
  13. +27 −8 mobile/src/main/res/layout/fragment_schedule.xml
  14. +3 −0 mobile/src/main/res/layout/item_session.xml
  15. +1 −0 mobile/src/main/res/values/strings.xml
  16. +1 −1 mobile/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt
  17. +33 −0 mobile/src/test/java/com/google/samples/apps/iosched/test/util/fakes/FakeFirebaseUserDataSource.kt
  18. +27 −0 mobile/src/test/java/com/google/samples/apps/iosched/test/util/fakes/FakeLoginDataSource.kt
  19. +45 −0 mobile/src/test/java/com/google/samples/apps/iosched/test/util/fakes/FakeLoginViewModelPlugin.kt
  20. +129 −0 mobile/src/test/java/com/google/samples/apps/iosched/ui/login/LoginViewModelPluginTest.kt
  21. +61 −7 mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModelTest.kt
  22. +8 −1 shared/build.gradle
  23. +33 −2 shared/src/debug/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt
  24. +91 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/login/FirebaseUserDataSource.kt
  25. +22 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/login/LoginDataSource.kt
  26. +26 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/login/LoginRemoteDataSource.kt
  27. +38 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/login/LoginRepository.kt
  28. +86 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/domain/ObservableUseCase.kt
  29. +36 −0 ...rc/main/java/com/google/samples/apps/iosched/shared/domain/login/ObservableFirebaseUserUseCase.kt
  30. +16 −10 shared/src/main/java/com/google/samples/apps/iosched/shared/util/Extensions.kt
  31. +62 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/util/WeakLiveDataHolder.kt
  32. +33 −2 shared/src/release/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt
  33. +31 −0 shared/src/staging/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt
  34. +169 −0 shared/src/test/java/com/google/samples/apps/iosched/shared/data/login/FirebaseUserDataSourceTest.kt
  35. +51 −0 shared/src/test/java/com/google/samples/apps/iosched/shared/data/login/LoginRepositoryTest.kt
  36. +79 −0 shared/src/test/java/com/google/samples/apps/iosched/shared/util/WeakLiveDataHolderTest.kt
  37. +33 −0 shared/src/test/java/com/google/tests/fakes/FakeFirebaseUserDataSource.kt
  38. +27 −0 shared/src/test/java/com/google/tests/fakes/FakeLoginDataSource.kt
View
@@ -49,9 +49,15 @@ buildscript {
googlePlayServicesClientVersion = "11.8.0"
googleServicesVersion = "3.2.0"
firebaseUiVersion = "3.2.2"
mockitoVersion = "2.8.9"
mockitoKotlinVersion = "1.5.0"
glideVersion = "4.6.1"
}
repositories {
maven {
url "$mdc_repo_location"
}
google()
jcenter()
}
View
@@ -86,6 +86,10 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$rootProject.dagger"
kapt "com.google.dagger:dagger-android-processor:$rootProject.dagger"
// Glide
implementation "com.github.bumptech.glide:glide:$rootProject.glideVersion"
annotationProcessor "com.github.bumptech.glide:compiler:$rootProject.glideVersion"
// Firebase
implementation("com.firebaseui:firebase-ui-auth:$rootProject.firebaseUiVersion") {
exclude group: 'com.android.support'
@@ -106,4 +110,7 @@ dependencies {
// Local unit tests
testImplementation "junit:junit:$rootProject.junitVersion"
testImplementation "org.mockito:mockito-core:$rootProject.mockitoVersion"
testImplementation "com.nhaarman:mockito-kotlin:$rootProject.mockitoKotlinVersion"
testImplementation "org.hamcrest:hamcrest-library:$rootProject.hamcrestVersion"
}
@@ -19,3 +19,10 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
@@ -16,9 +16,10 @@
package com.google.samples.apps.iosched.di
import com.google.samples.apps.iosched.shared.di.SharedModule
import com.google.samples.apps.iosched.MainApplication
import com.google.samples.apps.iosched.shared.di.SharedModule
import com.google.samples.apps.iosched.ui.MainModule
import com.google.samples.apps.iosched.ui.login.LoginViewModelPluginModule
import com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailModule
import com.google.samples.apps.iosched.util.login.LoginModule
import dagger.Component
@@ -32,17 +33,20 @@ import javax.inject.Singleton
* Whenever a new module is created, it should be added to the list of modules.
*/
@Singleton
@Component(modules = arrayOf(
AndroidSupportInjectionModule::class,
AppModule::class,
ViewModelModule::class,
SharedModule::class,
MainModule::class,
SessionDetailModule::class,
LoginModule::class))
@Component(
modules = arrayOf(
AndroidSupportInjectionModule::class,
AppModule::class,
ViewModelModule::class,
SharedModule::class,
MainModule::class,
SessionDetailModule::class,
LoginModule::class,
LoginViewModelPluginModule::class
)
)
interface AppComponent : AndroidInjector<MainApplication> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<MainApplication>()
}
@@ -0,0 +1,104 @@
/*
* Copyright 2018 Google LLC
*
* 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
*
* https://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.
*/
package com.google.samples.apps.iosched.ui.login
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.net.Uri
import com.google.firebase.auth.FirebaseUser
import com.google.samples.apps.iosched.shared.domain.login.ObservableFirebaseUserUseCase
import com.google.samples.apps.iosched.shared.result.Result
import com.google.samples.apps.iosched.shared.result.Result.Success
import com.google.samples.apps.iosched.shared.util.checkAllMatched
import com.google.samples.apps.iosched.shared.util.map
import com.google.samples.apps.iosched.ui.login.LoginEvent.RequestLogin
import com.google.samples.apps.iosched.ui.login.LoginEvent.RequestLogout
import com.google.samples.apps.iosched.ui.schedule.Event
import javax.inject.Inject
enum class LoginEvent {
RequestLogin, RequestLogout
}
/**
* Interface to implement login functionality in a ViewModel.
*
* You can inject a implementation of this via Dagger2, then use the implementation as an interface
* delegate to add login functionality without writing any code
*
* Example usage
*
* ```
* class MyViewModel @Inject constructor(
* loginViewModelComponent: LoginViewModelPlugin
* ) : ViewModel(), LoginViewModelPlugin by loginViewModelComponent {
* ```
*/
interface LoginViewModelPlugin {
/**
* Live updated value of the current firebase user
*/
val currentFirebaseUser: MutableLiveData<Result<FirebaseUser?>>
/**
* Live updated value of the current firebase users image url
*/
val currentUserImageUri: LiveData<Uri?>
/**
* Emits Events when a login event should be attempted
*/
val performLoginEvent: MutableLiveData<Event<LoginEvent>>
fun isLoggedIn(): Boolean {
val currentUser = currentFirebaseUser.value
return when (currentUser) {
is Success -> currentUser.data != null
else -> false
}.checkAllMatched
}
/**
* Emit an Event on performLoginEvent to request login
*/
fun emitLoginRequest() = performLoginEvent.postValue(Event(RequestLogin))
/**
* Emit an Event on performLoginEvent to request logout
*/
fun emitLogoutRequest() = performLoginEvent.postValue(Event(RequestLogout))
}
/**
* Implementation of LoginViewModel that can be used as an interface delegate.
*/
internal class LoginViewModelPluginImpl @Inject constructor(
observableFirebaseUserUseCase: ObservableFirebaseUserUseCase
) : LoginViewModelPlugin {
override val performLoginEvent = MutableLiveData<Event<LoginEvent>>()
override val currentFirebaseUser = MutableLiveData<Result<FirebaseUser?>>()
override val currentUserImageUri: LiveData<Uri?> = currentFirebaseUser.map { result ->
when (result) {
is Success -> result.data?.photoUrl
else -> null
}
}
init {
observableFirebaseUserUseCase(Unit, currentFirebaseUser)
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2018 Google LLC
*
* 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
*
* https://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.
*/
package com.google.samples.apps.iosched.ui.login
import com.google.samples.apps.iosched.shared.domain.login.ObservableFirebaseUserUseCase
import dagger.Module
import dagger.Provides
@Module
class LoginViewModelPluginModule {
@Provides
fun provideLoginViewModelPlugin(observableFirebaseUserUseCase: ObservableFirebaseUserUseCase):
LoginViewModelPlugin {
return LoginViewModelPluginImpl(observableFirebaseUserUseCase)
}
}
@@ -22,6 +22,7 @@ import android.graphics.Color.TRANSPARENT
import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.StateListDrawable
import android.net.Uri
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewPager
import android.support.v7.content.res.AppCompatResources
@@ -30,11 +31,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.bumptech.glide.Glide
import com.google.samples.apps.iosched.R
import com.google.samples.apps.iosched.databinding.ItemSessionTagBinding
import com.google.samples.apps.iosched.shared.model.Tag
import com.google.samples.apps.iosched.util.CircularOutlineProvider
import timber.log.Timber
@BindingAdapter("sessionTags")
fun sessionTags(container: LinearLayout, sessionTags: List<Tag>?) {
@@ -46,9 +51,9 @@ fun sessionTags(container: LinearLayout, sessionTags: List<Tag>?) {
}
private fun createSessionTagButton(
inflater: LayoutInflater,
container: ViewGroup,
sessionTag: Tag
inflater: LayoutInflater,
container: ViewGroup,
sessionTag: Tag
): Button {
val tagBinding = ItemSessionTagBinding.inflate(inflater, container, false).apply {
tag = sessionTag
@@ -76,8 +81,10 @@ fun tagColor(textView: TextView, color: Int) {
// for a state (different from transparent tint, which makes the drawable invisible).
val dotOrClear = StateListDrawable().apply {
// clear icon when checked
addState(intArrayOf(android.R.attr.state_checked),
drawableCompat(textView, R.drawable.tag_clear))
addState(
intArrayOf(android.R.attr.state_checked),
drawableCompat(textView, R.drawable.tag_clear)
)
// colored dot by default
addState(StateSet.WILD_CARD,
drawableCompat(textView, R.drawable.tag_dot)?.apply { setTint(tintColor) })
@@ -92,7 +99,7 @@ fun tagColor(textView: TextView, color: Int) {
addState(StateSet.WILD_CARD, drawableCompat(textView, R.drawable.tag_outline))
}
((textView.background as InsetDrawable).drawable as RippleDrawable)
.setDrawableByLayerId(R.id.tag_fill, tagBg)
.setDrawableByLayerId(R.id.tag_fill, tagBg)
}
fun tagTintOrDefault(color: Int, context: Context): Int {
@@ -109,3 +116,28 @@ fun drawableCompat(view: View, id: Int) = AppCompatResources.getDrawable(view.co
fun pageMargin(viewPager: ViewPager, pageMargin: Float) {
viewPager.pageMargin = pageMargin.toInt()
}
@BindingAdapter("clipToCircle")
fun clipToCircle(view: View, circleSize: Float) {
view.clipToOutline = true
view.outlineProvider = CircularOutlineProvider(circleSize.toInt())
}
@BindingAdapter("imageUrl")
fun imageUrl(imageView: ImageView, imageUrl: Uri?) {
when (imageUrl) {
null -> {
Timber.d("Unsetting image url")
// TODO: b/74393872 Use an actual placeholder.
Glide.with(imageView)
.load(R.drawable.tag_filled)
.into(imageView)
}
else -> {
Glide.with(imageView)
.load(imageUrl)
.into(imageView)
}
}
}
Oops, something went wrong.

0 comments on commit efd6167

Please sign in to comment.