Merge pull request #1 from medici/sg-login

MVVM architecture using Login screen
This commit is contained in:
Sandhya Koti
2019-11-06 14:38:34 +05:30
committed by GitHub Enterprise
14 changed files with 218 additions and 37 deletions

View File

@@ -4,6 +4,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def version_retrofit = '2.6.2'
def version_kotlin_coroutines = '1.3.2'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
@@ -21,15 +26,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 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-gson:$version_retrofit"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
}

View File

@@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.navi.medici.android_customer_app">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -9,7 +11,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name=".login.LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -1,12 +0,0 @@
package com.navi.medici.android_customer_app
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

View File

@@ -0,0 +1,11 @@
package com.navi.medici.android_customer_app.api
import retrofit2.Converter
import retrofit2.Retrofit
object RetrofitService {
fun build(baseURL: String, converterFactory: Converter.Factory): Retrofit {
val retrofitBuilder = Retrofit.Builder().baseUrl(baseURL)
return retrofitBuilder.addConverterFactory(converterFactory).build()
}
}

View File

@@ -0,0 +1,17 @@
package com.navi.medici.android_customer_app.api
import retrofit2.Response
import java.io.IOException
abstract class SafeApiRequest {
suspend fun <T : Any> apiRequest(call: suspend () -> Response<T>): T {
val response = call.invoke()
if (response.isSuccessful) {
return response.body()!!
} else {
throw ApiException(response.code().toString())
}
}
}
class ApiException(message: String) : IOException(message)

View File

@@ -0,0 +1,9 @@
package com.navi.medici.android_customer_app.login
data class Login(
val id: Int? = 0,
val userId: Int? = 0,
val title: String? = ""
) {
var phoneNumber: String = ""
}

View File

@@ -0,0 +1,23 @@
package com.navi.medici.android_customer_app.login
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProviders
import com.navi.medici.android_customer_app.R
import com.navi.medici.android_customer_app.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
private lateinit var loginViewModel: LoginViewModel
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
binding.phoneEdit.addTextChangedListener { loginViewModel.onChangePhoneNumber(it.toString()) }
}
}

View File

@@ -0,0 +1,21 @@
package com.navi.medici.android_customer_app.login
import com.navi.medici.android_customer_app.api.RetrofitService
import retrofit2.Response
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val gsonConverterFactory = GsonConverterFactory.create()
interface LoginApi {
@GET("posts/1")
suspend fun submitPhoneNumber(): Response<Login>
companion object {
operator fun invoke(): LoginApi {
return RetrofitService.build(BASE_URL, gsonConverterFactory)
.create(LoginApi::class.java)
}
}
}

View File

@@ -0,0 +1,7 @@
package com.navi.medici.android_customer_app.login
import com.navi.medici.android_customer_app.api.SafeApiRequest
class LoginRepository(private val loginApi: LoginApi) : SafeApiRequest() {
suspend fun submitPhoneNumber(phoneNumber: String) = apiRequest { loginApi.submitPhoneNumber() }
}

View File

@@ -0,0 +1,27 @@
package com.navi.medici.android_customer_app.login
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class LoginViewModel : ViewModel() {
private val login = Login()
private val loginRepository = LoginRepository(LoginApi())
private val coroutineScope = CoroutineScope(Dispatchers.Main)
fun onChangePhoneNumber(phoneNumber: String) {
if (isValidPhoneNumber(phoneNumber)) {
login.phoneNumber = phoneNumber
submitPhoneNumber()
}
}
private fun submitPhoneNumber() {
coroutineScope.launch {
loginRepository.submitPhoneNumber(login.phoneNumber)
}
}
private fun isValidPhoneNumber(phoneNumber: String): Boolean = phoneNumber.length == 10
}

View File

@@ -0,0 +1,77 @@
<?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">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/country_code_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/country_code"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="@+id/phone_edit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/phone_edit" />
<EditText
android:id="@+id/phone_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="32dp"
android:backgroundTint="@android:color/darker_gray"
android:ems="10"
android:importantForAutofill="no"
android:inputType="phone"
android:maxLength="10"
android:textColorHighlight="@android:color/darker_gray"
android:textColorLink="@android:color/darker_gray"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/country_code_text"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/phone_label_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginBottom="40dp"
android:text="@string/phone_number_label"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/phone_edit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/country_code_text" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="56dp"
android:text="@string/create_new_account"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/phone_label_text"
app:layout_constraintStart_toStartOf="@+id/phone_label_text" />
<TextView
android:id="@+id/terms_conditions_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:layout_marginEnd="32dp"
android:text="@string/terms_and_conditions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/country_code_text"
app:layout_constraintTop_toBottomOf="@+id/phone_edit" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,3 +1,7 @@
<resources>
<string name="app_name">android-customer-app</string>
<string name="app_name">navi</string>
<string name="country_code">+91</string>
<string name="phone_number_label">Enter your mobile number to register with Navi</string>
<string name="create_new_account">Create New Account</string>
<string name="terms_and_conditions"><![CDATA[By proceeding, you agree to our Terms & Conditions & Privacy Policy. Standard operator charges may apply for sms.]]></string>
</resources>

View File

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