Splash Screen
splash screen는 별 거는 없다. Splash Screen을 구성하는 xml 파일 작성하고 몇 초 딜레이 후 LoginActivity로 이동하도록 한다.
activity_splash.xml
우선 Splash Screen을 구성하는 activity_splash.xml을 작성한다.
간단하게 보라색 화면에 흰 text로 app name을 화면 중앙에 띄어줬다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".SplashActivity"
android:background="@color/purple_500">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="50dp"/>
</RelativeLayout>
NoActionBar
ActionBar가 뜨면 화면이 예쁘지 않기 때문에 ActionBar을 없애주기로 한다.
res > values > themes 에서 themes.xml을 변경하도록 한다. styple을 아래처럼 NoActionBar로 변경해준다.
<style name="Theme.FoundDog" parent="Theme.MaterialComponents.DayNight.NoActionBar">
Status Bar
res > values > themes 의 themes.xml에서 Status Bar (상태바)도 변경 가능하다. Status Bar의 색상을 변경할 수도 있고 Status Bar를 투명하게 할 수도 있다. 아래 사진의 코드는 Status Bar의 색상을 정하는 부분이다.
temes.xml의 status bar
Status Bar를 투명하게 하면 화면이 Status Bar가 있는 부분까지 채워져서 우선Status Bar를 투명하게 하지 않았다.
Status Bar를 투명하게 하려면 themes.xml에 아래 코드를 추가하면 된다.
<item name="android:windowTranslucentStatus">true</item>
SplashActivity.kt
package com.example.founddog
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.view.WindowInsets
import android.view.WindowManager
class SplashActivity : AppCompatActivity() {
private val SPLASH_TIME: Long = 1500
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
// Full Screen
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else {
window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN )
}
// LoginActivity 열기
@Suppress("DEPRECATION")
Handler().postDelayed(
{
startActivity(Intent(this@SplashActivity, LoginActivity::class.java))
finish()
},
SPLASH_TIME
)
}
}
AndroidManifest.xml
앱을 실행하면 SplashActivity가 열리도록 AndroidManifest의 intent-filter 속성을 SplshActivity 밑으로 수정한다.
<activity android:name=".SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Login / Register
Firebase 프로젝트에 앱 추가하기
Firebase 에서 프로젝트를 생성한 후 Firebase가 시키는대로만 하면 된다.
Firebase Authentication 시작하기
Firebase auth 공식문서 를 참고한다.
만든 Firebase 프로젝트에서 Authentication > 이메일/비밀번호를 사용 설정하고 저장한다.
Firebase Authentication에서 이메일/비밀번호 사용설정
Android Studio에서 Firebase Auth를 사용하기 위해 아래 Library를 imiport한다.
implementation 'com.google.firebase:firebase-auth-ktx'
Login.xml
네 모서리가 둥근 Button을 만들기 위해 res > drawable에 button.xml을 추가한다. 아래는 button.xml 전체 코드이다.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:radius="@dimen/btn_radius"/>
</shape>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:gravity="center"
tools:context=".LoginActivity">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edt_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/edt_email"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:passwordToggleEnabled="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edt_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="@string/edt_pwd"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="@string/btn_login"/>
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="@string/btn_register"/>
</LinearLayout>
TextInputLayout을 사용하기 위해 아래 Library를 imiport한다.
implementation 'com.google.android.material:material:1.2.1'
Register.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:gravity="center"
tools:context=".LoginActivity">
<ImageView
android:id="@+id/img_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_clear"/>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edt_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/edt_email"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:passwordToggleEnabled="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edt_pwd1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="@string/edt_pwd"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:passwordToggleEnabled="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edt_pwd2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:hint="@string/edt_pwd2"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button"
android:text="@string/btn_submit"/>
</LinearLayout>
LoginActivity.kt
package com.example.founddog
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_login.*
class LoginActivity : AppCompatActivity(), View.OnClickListener {
companion object{
const val TAG: String = "LoginActivity"
}
lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
auth = Firebase.auth
btn_login.setOnClickListener(this)
btn_register.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.btn_login -> {
loginUser()
}
R.id.btn_register -> {
startActivity(Intent(this@LoginActivity, RegisterActivity::class.java))
}
}
}
private fun loginUser() {
val email = edt_email.text.toString().trim { it <= ' ' }
val pwd = edt_pwd.text.toString().trim { it <= ' ' }
if(validateLoginDetails(email, pwd)) {
auth.signInWithEmailAndPassword(email, pwd)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithEmail:success")
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
finish()
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithEmail:failure", task.exception)
Toast.makeText(
baseContext, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun validateLoginDetails(email : String, pwd : String): Boolean {
return when {
TextUtils.isEmpty(email) -> {
Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
false
}
TextUtils.isEmpty(pwd) -> {
Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
false
}
else -> {
true
}
}
}
}
RegisterActivity.kt
package com.example.founddog
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import kotlinx.android.synthetic.main.activity_register.*
class RegisterActivity : AppCompatActivity(), View.OnClickListener {
companion object{
const val TAG: String = "RegisterActivity"
}
lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
auth = Firebase.auth
btn_submit.setOnClickListener(this)
img_clear.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.btn_submit -> {
register()
}
R.id.img_clear -> {
finish()
}
}
}
fun register() {
val email = edt_email.text.toString().trim { it <= ' ' }
val pwd1 = edt_pwd1.text.toString().trim { it <= ' ' }
val pwd2 = edt_pwd2.text.toString().trim { it <= ' ' }
if(validateRegisterDetails(email, pwd1, pwd2)) {
auth.createUserWithEmailAndPassword(email, pwd1)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "createUserWithEmail:success")
finish()
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "createUserWithEmail:failure", task.exception)
Toast.makeText(
baseContext, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun validateRegisterDetails(email : String, pwd1 : String, pwd2 : String): Boolean {
return when {
TextUtils.isEmpty(email) -> {
Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
false
}
TextUtils.isEmpty(pwd1) -> {
Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
false
}
TextUtils.isEmpty(pwd2) -> {
Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
false
}
pwd1 != pwd2 -> {
Toast.makeText(this,"no email", Toast.LENGTH_SHORT).show()
false
}
else -> {
true
}
}
}
}
추가할 것!
activity_login.xml / activity_register.xml
Layout 간의 margin 같은 건 하나도 지정 안 했다.
좀더 이쁘고 보기 편하기 바꿀 것
Activity의 finish()
activity의 life cycle 공부 후에 activity를 finish()할 타이밍을 정리해서 수정할 것
Log 출력
오류 같은 거 잘 출력하도록 할 것
TextInputLayout
email 양식을 틀리게 적거나 비밀번호 오류 날 때 TextInputLayout에서 테두리 color 변경 + 오류 출력하도록 코드 추가할 것