Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# LiveData
Playground for setting up LiveData
# LiveDataBinding Playground

Architecture: MVVM
Language: Kotlin
Android Architecture Components: LiveData, Data Binding

A very bare bone example of using MVVM with a couple of android architectural components
16 changes: 13 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'
apply plugin: "kotlin-kapt"

android {
compileSdkVersion 28
buildToolsVersion "28.0.3"

defaultConfig {
applicationId "com.example.livedata"
minSdkVersion 23
Expand All @@ -20,14 +21,23 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled true
}
}


dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

def life_versions = "1.1.1"

implementation "android.arch.lifecycle:extensions:$life_versions"
annotationProcessor "android.arch.lifecycle:compiler:$life_versions"
}
8 changes: 5 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.livedata"
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.livedata"
>

<application
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
Expand All @@ -14,6 +15,7 @@
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.VIEW"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/example/livedata/BaseViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.livedata

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

open class BaseViewModel : ViewModel() {
protected var <T : Any> LiveData<T>.latestValue: T?
get() = this.value
set(value) {
(this as MutableLiveData<T>).value = value
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new line

69 changes: 69 additions & 0 deletions app/src/main/java/com/example/livedata/LoginFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.livedata

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import com.example.livedata.LoginErrorType.EmailEmpty
import com.example.livedata.LoginErrorType.PasswordEmpty
import com.example.livedata.LoginErrorType.PasswordShort
import com.example.livedata.databinding.FragmentLoginBinding
import kotlinx.android.synthetic.main.fragment_login.password
import kotlinx.android.synthetic.main.fragment_login.resultEmailAddress
import kotlinx.android.synthetic.main.fragment_login.resultPassword
import kotlinx.android.synthetic.main.fragment_login.username

class LoginFragment : Fragment() {
private lateinit var viewModel: LoginViewModel
private lateinit var binding: FragmentLoginBinding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding = FragmentLoginBinding.inflate(inflater)
return binding.apply {
lifecycleOwner = viewLifecycleOwner
loginViewModel = viewModel

}.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindViewModel()
}

private fun bindViewModel() {
username.bindError(viewModel.emailError)
password.bindError(viewModel.passwordError)

//TODO: remove this logic and the result views when actually implementing login
viewModel.user.observe(this, Observer {
it?.apply {
resultEmailAddress.text = username
resultPassword.text = password
}
})
}

private fun EditText.bindError(liveDataError: LiveData<LoginErrorType>) {
liveDataError.observe(viewLifecycleOwner, Observer {
it?.let { loginErrorType -> this.setErrorOnField(loginErrorType) }
})
}

private fun EditText.setErrorOnField(errorType: LoginErrorType) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! Sealed class 🔥

error =
when (errorType) {
is EmailEmpty -> getString(R.string.empty_email_error)
is PasswordEmpty -> getString(R.string.empty_password_error)
is PasswordShort -> getString(R.string.password_too_short_error)
}
requestFocus()
}
}
33 changes: 33 additions & 0 deletions app/src/main/java/com/example/livedata/LoginViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.livedata

import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import com.example.livedata.LoginErrorType.EmailEmpty
import com.example.livedata.LoginErrorType.PasswordEmpty
import com.example.livedata.LoginErrorType.PasswordShort

const val PASSWORD_LENGTH = 5

class LoginViewModel : BaseViewModel() {
val password = MutableLiveData<String>()
val username = MutableLiveData<String>()
val emailError: LiveData<LoginErrorType> = MutableLiveData<LoginErrorType>()
val passwordError: LiveData<LoginErrorType> = MutableLiveData<LoginErrorType>()

var user: LiveData<User> = MutableLiveData<User>()

fun onClick() {
when {
username.value.isNullOrEmpty() -> emailError.latestValue = EmailEmpty
password.value.isNullOrEmpty() -> passwordError.latestValue = PasswordEmpty
password.value.orEmpty().length < PASSWORD_LENGTH -> passwordError.latestValue = PasswordShort
else -> user.latestValue = User(username.value.orEmpty(), password.value.orEmpty())
}
}
}

sealed class LoginErrorType {
object EmailEmpty : LoginErrorType()
object PasswordShort : LoginErrorType()
object PasswordEmpty : LoginErrorType()
}
8 changes: 6 additions & 2 deletions app/src/main/java/com/example/livedata/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.example.livedata

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragment = LoginFragment()
supportFragmentManager
.beginTransaction()
.add(R.id.container, fragment)
.commit()
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/example/livedata/User.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.livedata

data class User(
val username: String,
val password: String
)
16 changes: 5 additions & 11 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
109 changes: 109 additions & 0 deletions app/src/main/res/layout/fragment_login.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
>

<data>

<variable
name="loginViewModel"
type="com.example.livedata.LoginViewModel"
/>
</data>


<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_md"
tools:context=".View.MainActivity"
>

<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="username"
android:ems="10"
android:hint="@string/username_field_placeholder"
android:text="@={loginViewModel.username}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UnusedAttribute"
/>

<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_md"
android:autofillHints="password"
android:ems="10"
android:hint="@string/password_field_hint"
android:inputType="textPassword"
android:text="@={loginViewModel.password}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username"
tools:targetApi="o"
/>

<Button
android:id="@+id/loginButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_lg"
android:onClick="@{() -> loginViewModel.onClick()}"
android:text="@string/login_button_text"
android:textSize="@dimen/text_md"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password"
/>

<TextView
android:id="@+id/resultsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_sm"
android:layout_marginTop="@dimen/spacing_xlg"
android:layout_marginEnd="@dimen/spacing_sm"
android:gravity="center"
android:text="@string/live_data_results_title"
android:textColor="@android:color/background_dark"
android:textSize="@dimen/text_md"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginButton"
/>

<TextView
android:id="@+id/resultEmailAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_sm"
android:text="@string/place_holder_text"
android:textColor="@android:color/holo_blue_light"
android:textSize="@dimen/text_md"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/resultsTitle"
/>

<TextView
android:id="@+id/resultPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_sm"
android:text="@string/place_holder_text"
android:textColor="@android:color/holo_blue_light"
android:textSize="@dimen/text_md"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/resultEmailAddress"
/>

</android.support.constraint.ConstraintLayout>
</layout>
9 changes: 9 additions & 0 deletions app/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="spacing_sm">8dp</dimen>
<dimen name="spacing_md">16dp</dimen>
<dimen name="spacing_lg">32dp</dimen>
<dimen name="spacing_xlg">60dp</dimen>

<dimen name="text_md">20sp</dimen>
</resources>
8 changes: 8 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<resources>
<string name="app_name">LiveData</string>
<string name="live_data_results_title">Results of LiveDataBinding</string>
<string name="place_holder_text">– –</string>
<string name="login_button_text">Login</string>
<string name="password_field_hint">Password</string>
<string name="username_field_placeholder">Username</string>
<string name="empty_email_error">Enter an E-Mail Address</string>
<string name="empty_password_error">Enter a Password</string>
<string name="password_too_short_error">Enter at least 6 Digit password</string>
</resources>
6 changes: 1 addition & 5 deletions app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

</resources>