Merge pull request #1 from medici/sg-login
MVVM architecture using Login screen
This commit is contained in:
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 = ""
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
77
app/src/main/res/layout/activity_login.xml
Normal file
77
app/src/main/res/layout/activity_login.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user