① LoginActivity를 생성한다.
class LoginActivity : AppCompatActivity() {
lateinit var binding : ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
② activity_login.xml에 아래의 내용을 입력한다.
<?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">
<ImageView
android:id="@+id/login_close_iv"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="5dp"
android:src="@drawable/btn_actionbar_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/login_flo_logo_iv"
android:layout_width="80dp"
android:layout_height="30dp"
android:src="@drawable/ic_flo_logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_close_iv" />
<EditText
android:id="@+id/login_id_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="80dp"
android:layout_marginEnd="5dp"
android:background="@null"
android:hint="아이디(이메일)"
android:inputType="text"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/login_at_sign_tv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_flo_logo_iv" />
<View
android:id="@+id/login_id_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/login_id_et"
app:layout_constraintStart_toStartOf="@+id/login_id_et"
app:layout_constraintTop_toBottomOf="@+id/login_id_et"/>
<TextView
android:id="@+id/login_at_sign_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@color/black"
android:textStyle="bold"
android:text="\@"
app:layout_constraintBottom_toBottomOf="@+id/login_id_et"
app:layout_constraintEnd_toEndOf="@+id/login_flo_logo_iv"
app:layout_constraintStart_toStartOf="@+id/login_flo_logo_iv"
app:layout_constraintTop_toTopOf="@+id/login_id_et" />
<EditText
android:id="@+id/login_direct_input_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="20dp"
android:background="@null"
android:hint="직접입력"
android:inputType="textWebEmailAddress"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/login_id_et"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/login_at_sign_tv"
app:layout_constraintTop_toTopOf="@+id/login_id_et" />
<View
android:id="@+id/login_address_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/login_direct_input_et"
app:layout_constraintStart_toStartOf="@+id/login_direct_input_et"
app:layout_constraintTop_toBottomOf="@+id/login_direct_input_et"/>
<ImageView
android:id="@+id/login_email_list_iv"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@drawable/nugu_btn_down"
app:layout_constraintBottom_toBottomOf="@+id/login_direct_input_et"
app:layout_constraintEnd_toEndOf="@+id/login_direct_input_et"
app:layout_constraintTop_toTopOf="@+id/login_direct_input_et" />
<EditText
android:id="@+id/login_password_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="20dp"
android:background="@null"
android:hint="비밀번호"
android:inputType="textPassword"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_id_underscore_view" />
<View
android:id="@+id/login_password_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/login_password_et"
app:layout_constraintStart_toStartOf="@+id/login_password_et"
app:layout_constraintTop_toBottomOf="@+id/login_password_et"/>
<ImageView
android:id="@+id/login_hide_password_iv"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="5dp"
android:src="@drawable/btn_input_password"
app:layout_constraintBottom_toBottomOf="@+id/login_password_et"
app:layout_constraintEnd_toEndOf="@+id/login_password_et"
app:layout_constraintTop_toTopOf="@+id/login_password_et" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/login_sign_in_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="20dp"
android:background="@drawable/button_background_flo_color"
android:text="로그인"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_password_underscore_view" />
<TextView
android:id="@+id/login_find_id_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="아이디 찾기"
app:layout_constraintStart_toStartOf="@+id/login_sign_in_btn"
app:layout_constraintTop_toBottomOf="@+id/login_sign_in_btn" />
<TextView
android:id="@+id/login_division_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="|"
app:layout_constraintBottom_toBottomOf="@+id/login_find_id_tv"
app:layout_constraintStart_toEndOf="@+id/login_find_id_tv"
app:layout_constraintTop_toTopOf="@+id/login_find_id_tv" />
<TextView
android:id="@+id/login_find_password_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="비밀번호 찾기"
app:layout_constraintBottom_toBottomOf="@+id/login_division_tv"
app:layout_constraintStart_toEndOf="@+id/login_division_tv"
app:layout_constraintTop_toTopOf="@+id/login_division_tv" />
<TextView
android:id="@+id/login_sign_up_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="회원가입"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/login_sign_in_btn"
app:layout_constraintTop_toBottomOf="@+id/login_sign_in_btn" />
<LinearLayout
android:id="@+id/login_t_world_login_layout"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="35dp"
android:layout_marginEnd="20dp"
android:background="@drawable/button_background_black_color"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_find_id_tv">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ico_20_logo_tid_white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="아이디로 로그인"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/login_phone_number_login_layout"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="20dp"
android:background="@drawable/button_background_white_color"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_t_world_login_layout">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="4dp"
android:scaleType="fitXY"
android:src="@drawable/btn_setting_phone" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="휴대폰 번호 로그인"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/login_social_login_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_phone_number_login_layout">
<ImageView
android:id="@+id/login_naver_login_iv"
android:layout_width="45dp"
android:layout_height="45dp"
android:src="@drawable/naver_44" />
<ImageView
android:id="@+id/login_kakako_login_iv"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:src="@drawable/kakako_44" />
<ImageView
android:id="@+id/login_apple_login_iv"
android:layout_width="45dp"
android:layout_height="45dp"
android:src="@drawable/apple_44" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
③ SignUpActivity를 생성한다.
class SignUpActivity : AppCompatActivity() {
lateinit var binding: ActivitySignUpBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignUpBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
④ activity_sign_up.xml에 아래의 내용을 입력한다.
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/sign_up_sign_up_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="회원가입"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/sign_up_id_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="5dp"
android:background="@null"
android:hint="아이디(이메일)"
android:inputType="text"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/sign_up_at_sign_tv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sign_up_sign_up_tv" />
<View
android:id="@+id/sign_up_id_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/sign_up_id_et"
app:layout_constraintStart_toStartOf="@+id/sign_up_id_et"
app:layout_constraintTop_toBottomOf="@+id/sign_up_id_et"/>
<TextView
android:id="@+id/sign_up_at_sign_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="\@"
android:textColor="@color/black"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/sign_up_id_et"
app:layout_constraintEnd_toEndOf="@+id/sign_up_sign_up_tv"
app:layout_constraintStart_toStartOf="@+id/sign_up_sign_up_tv"
app:layout_constraintTop_toTopOf="@+id/sign_up_id_et" />
<EditText
android:id="@+id/sign_up_direct_input_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="20dp"
android:background="@null"
android:hint="직접입력"
android:inputType="textWebEmailAddress"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/sign_up_id_et"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/sign_up_at_sign_tv"
app:layout_constraintTop_toTopOf="@+id/sign_up_at_sign_tv" />
<View
android:id="@+id/sign_up_address_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/sign_up_direct_input_et"
app:layout_constraintStart_toStartOf="@+id/sign_up_direct_input_et"
app:layout_constraintTop_toBottomOf="@+id/sign_up_direct_input_et"/>
<ImageView
android:id="@+id/sign_up_email_list_iv"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginEnd="5dp"
android:src="@drawable/nugu_btn_down"
app:layout_constraintBottom_toBottomOf="@+id/sign_up_direct_input_et"
app:layout_constraintEnd_toEndOf="@+id/sign_up_direct_input_et"
app:layout_constraintTop_toTopOf="@+id/sign_up_direct_input_et" />
<TextView
android:id="@+id/sign_up_email_error_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/form_error"
android:textSize="14sp"
android:textStyle="bold"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sign_up_id_underscore_view"/>
<EditText
android:id="@+id/sign_up_name_et"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:background="@null"
android:hint="이름"
android:inputType="textPersonName"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sign_up_email_error_tv" />
<View
android:id="@+id/sign_up_name_underscore_view"
android:layout_width="150dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintStart_toStartOf="@+id/sign_up_password_et"
app:layout_constraintTop_toBottomOf="@+id/sign_up_name_et"/>
<EditText
android:id="@+id/sign_up_password_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="20dp"
android:background="@null"
android:hint="비밀번호"
android:inputType="textPassword"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sign_up_name_et" />
<View
android:id="@+id/sign_up_password_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/sign_up_password_et"
app:layout_constraintStart_toStartOf="@+id/sign_up_password_et"
app:layout_constraintTop_toBottomOf="@+id/sign_up_password_et"/>
<ImageView
android:id="@+id/sign_up_hide_password_iv"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="5dp"
android:src="@drawable/btn_input_password"
app:layout_constraintBottom_toBottomOf="@+id/sign_up_password_et"
app:layout_constraintEnd_toEndOf="@+id/sign_up_password_et"
app:layout_constraintTop_toTopOf="@+id/sign_up_password_et" />
<EditText
android:id="@+id/sign_up_password_check_et"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="20dp"
android:background="@null"
android:hint="비밀번호 확인"
android:inputType="textPassword"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sign_up_password_underscore_view" />
<View
android:id="@+id/sign_up_password_check_underscore_view"
android:layout_width="0dp"
android:layout_height="1px"
android:layout_marginTop="15dp"
android:background="#a8a8a8"
app:layout_constraintEnd_toEndOf="@+id/sign_up_password_check_et"
app:layout_constraintStart_toStartOf="@+id/sign_up_password_check_et"
app:layout_constraintTop_toBottomOf="@+id/sign_up_password_check_et"/>
<ImageView
android:id="@+id/sign_up_hide_password_check_iv"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="5dp"
android:src="@drawable/btn_input_password"
app:layout_constraintBottom_toBottomOf="@+id/sign_up_password_check_et"
app:layout_constraintEnd_toEndOf="@+id/sign_up_password_check_et"
app:layout_constraintTop_toTopOf="@+id/sign_up_password_check_et" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/sign_up_sign_up_btn"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/button_background_flo_color"
android:text="가입완료"
android:textColor="@color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ProgressBar
android:id="@+id/sign_up_loading_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:indeterminateTint="@color/flo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
① LockerFragment의 로그인 버튼을 클릭했을 때, LoginActivity로 전환되도록 만들어주자.
binding.lockerLoginTv.setOnClickListener {
val intent = Intent(requireActivity(), LoginActivity::class.java)
startActivity(intent)
}
② LoginActivity의 회원가입 버튼을 눌렀을 때, SignUpActivity로 전환되도록 만든다.
binding.loginSignUpTv.setOnClickListener {
val intent = Intent(this, SignUpActivity::class.java)
startActivity(intent)
}
③ 회원가입한 유저의 정보를 저장하기 위해 User 테이블을 생성한다.
@Entity(tableName = "UserTable")
data class User(
var email : String,
var password : String
) {
@PrimaryKey(autoGenerate = true) var id : Int = 0
}
④ UserDao를 생성한다.
@Dao
interface UserDao {
@Insert
fun insert(user : User)
@Query("select * from UserTable")
fun getUsers() : List<User>
@Query("select * from UserTable where email =:email and password = :password")
fun getUser(email : String, password : String) : User?
}
⑤ SongDatabase에 User 테이블과 UserDao를 추가한다.
@Database(entities = [Song::class, Album::class, User::class], version = 1)
abstract class SongDatabase: RoomDatabase() {
abstract fun albumDao(): AlbumDao
abstract fun songDao(): SongDao
abstract fun userDao() : UserDao
...
⑥ SignUpActivity에 아래의 내용을 입력한다.
class SignUpActivity : AppCompatActivity() {
lateinit var binding: ActivitySignUpBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignUpBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.signUpSignUpBtn.setOnClickListener {
val signUpCompletion = signUp()
if(signUpCompletion) {
finish()
}
}
}
private fun getUser() : User {
val email : String = binding.signUpIdEt.text.toString() + "@" + binding.signUpDirectInputEt.text.toString()
val pwd : String = binding.signUpPasswordEt.text.toString()
return User(email, pwd)
}
private fun signUp() : Boolean {
if(binding.signUpIdEt.text.toString().isEmpty() || binding.signUpDirectInputEt.text.toString().isEmpty()) {
Toast.makeText(this, "이메일 형식이 잘못되었습니다.", Toast.LENGTH_SHORT).show()
return false
}
if(binding.signUpPasswordEt.text.toString() != binding.signUpPasswordCheckEt.text.toString()) {
Toast.makeText(this, "비밀번호가 일치하지 않습니다.", Toast.LENGTH_SHORT).show()
return false
}
val userDB = SongDatabase.getInstance(this)!!
userDB.userDao().insert(getUser())
val user = userDB.userDao().getUsers()
Log.d("sign-up", user.toString())
Toast.makeText(this, "회원가입이 완료되었습니다.", Toast.LENGTH_SHORT).show()
return true
}
}
이제 코드를 실행시켜보자. 예외가 잘 처리되고, 회원가입도 잘 진행된다.
① LoginActivity에 아래의 내용을 입력한다.
class LoginActivity : AppCompatActivity() {
lateinit var binding : ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.loginSignInBtn.setOnClickListener {
login()
}
}
private fun login() {
if (binding.loginIdEt.text.toString().isEmpty() || binding.loginDirectInputEt.text.toString().isEmpty()) {
Toast.makeText(this, "이메일을 입력해주세요", Toast.LENGTH_SHORT).show()
return
}
if (binding.loginPasswordEt.text.toString().isEmpty()) {
Toast.makeText(this, "비밀번호를 입력해주세요", Toast.LENGTH_SHORT).show()
return
}
val email : String = binding.loginIdEt.text.toString() + "@" + binding.loginDirectInputEt.text.toString()
val pwd : String = binding.loginPasswordEt.text.toString()
val songDB = SongDatabase.getInstance(this)!!
val user = songDB.userDao().getUser(email, pwd)
if (user != null) {
Log.d("LoginActivity", user.id.toString())
saveJwt(user.id)
startMainActivity()
} else {
Toast.makeText(this, "회원 정보가 존재하지 않습니다", Toast.LENGTH_SHORT).show()
}
}
private fun saveJwt(jwt : Int) {
val spf = getSharedPreferences("auth", MODE_PRIVATE)
val editor = spf.edit()
editor.putInt("jwt", jwt)
editor.apply()
}
private fun startMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
② LockerFragment에서 로그인한 유저에게는 로그아웃 버튼이, 로그인하지 않은 유저에게는 로그인 버튼이 표시되도록 만들어주자.
class LockerFragment : Fragment() {
...
override fun onStart() {
super.onStart()
initViews()
}
private fun getJwt() : Int {
val spf = requireActivity().getSharedPreferences("auth", AppCompatActivity.MODE_PRIVATE)
return spf!!.getInt("jwt", 0)
}
private fun initViews() {
val jwt : Int = getJwt()
if (jwt == 0) {
binding.lockerLoginTv.text="로그인"
binding.lockerLoginTv.setOnClickListener {
startActivity(Intent(requireActivity(), LoginActivity::class.java))
}
}
else {
binding.lockerLoginTv.text = "로그아웃"
binding.lockerLoginTv.setOnClickListener {
logout()
startActivity(Intent(requireActivity(), MainActivity::class.java))
}
}
}
private fun logout() {
val spf = activity?.getSharedPreferences("auth", AppCompatActivity.MODE_PRIVATE)
val editor = spf!!.edit()
editor.remove("jwt")
editor.apply()
}
}
코드를 실행시켜보자. 데이터베이스에 존재하지 않는 ID/PW로 로그인하는 경우에 대해 잘 처리하고 있고, 로그인도 정상적으로 진행된다.
로그인 기능이 추가됨에 따라, 사용자 별로 좋아요 데이터를 저장할 필요가 생겼다. 지금부터 사용자 별로 좋아요를 저장할 수 있는 데이터베이스를 구축해보자.
① Like data class를 생성한다.
@Entity(tableName = "LikeTable")
data class Like(
var userId : Int,
var albumId : Int
) {
@PrimaryKey(autoGenerate = true) var id : Int = 0
}
② AlbumDao에 아래의 쿼리문을 추가한다.
@Insert
fun likeAlbum(like : Like)
@Query("select id from LikeTable where userId =:userId and albumId = :albumId")
fun isLikedAlbum(userId : Int, albumId : Int) : Int?
@Query("delete from LikeTable where userId =:userId and albumId = :albumId")
fun dislikedAlbum(userId : Int, albumId : Int)
@Query("select at.* from LikeTable as lt left join AlbumTable as at on lt.albumId = at.id where lt.userId = :userId")
fun getLikedAlbums(userId : Int) : List<Album>
③ SongDatabase에 Like 테이블을 추가한다.
@Database(entities = [Song::class, Album::class, User::class, Like::class], version = 1)
④ AlbumFragment에 아래의 내용을 추가한다.
class AlbumFragment : Fragment() {
lateinit var binding: FragmentAlbumBinding
private var gson: Gson = Gson()
private var isLiked : Boolean = false
...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentAlbumBinding.inflate(inflater,container,false)
val albumToJson = arguments?.getString("album")
val album = gson.fromJson(albumToJson, Album::class.java)
isLiked = isLikedAlbum(album.id)
setInit(album)
setOnClickListener(album)
...
private fun getJwt() : Int {
val spf = requireActivity().getSharedPreferences("auth", AppCompatActivity.MODE_PRIVATE)
return spf.getInt("jwt", 0)
}
private fun likeAlbum(userId : Int, albumId : Int) {
val songDB = SongDatabase.getInstance(requireActivity())!!
val like = Like(userId, albumId)
songDB.albumDao().likeAlbum(like)
}
private fun isLikedAlbum(albumId : Int) : Boolean {
val songDB = SongDatabase.getInstance(requireActivity())!!
val userId = getJwt()
val likeId : Int? = songDB.albumDao().isLikedAlbum(userId, albumId)
return likeId != null
}
private fun disLikeAlbum(albumId : Int) {
val songDB = SongDatabase.getInstance(requireActivity())!!
val userId = getJwt()
songDB.albumDao().dislikedAlbum(userId, albumId)
}
private fun setOnClickListener(album : Album) {
val userId = getJwt()
binding.albumLikeIv.setOnClickListener {
if(isLiked) {
binding.albumLikeIv.setImageResource(R.drawable.ic_my_like_off)
disLikeAlbum(album.id)
}
else {
binding.albumLikeIv.setImageResource((R.drawable.ic_my_like_on))
likeAlbum(userId, album.id)
}
}
}
}
① SavedAlbumFragment를 생성한다.
② SavedAlbumFragment를 LockerFragment의 TabLayout에 포함시키기 위해 LockerVPAdapter를 아래와 같이 수정한다.
class LockerVPAdapter (fragment : Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when(position){
0 -> SavedSongFragment()
1 -> MusicFileFragment()
else -> SavedAlbumFragment()
}
}
}
③ LockerFragment의 information을 아래와 같이 수정한다.
private val information = arrayListOf("저장한곡", "음악파일", "저장앨범")
④ fragment_locker.xml 파일에서 TabLayout의 width를 wrap_content로 변경한다.
⑤ SavedAlbumRVAdapter 클래스를 생성한다.
class SavedAlbumRVAdapter (): RecyclerView.Adapter<SavedAlbumRVAdapter.ViewHolder>() {
private val albums = ArrayList<Album>()
interface MyItemClickListener{
fun onRemoveSong(songId: Int)
}
private lateinit var mItemClickListener: MyItemClickListener
fun setMyItemClickListener(itemClickListener: MyItemClickListener){
mItemClickListener = itemClickListener
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): SavedAlbumRVAdapter.ViewHolder {
val binding: ItemLockerAlbumBinding = ItemLockerAlbumBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: SavedAlbumRVAdapter.ViewHolder, position: Int) {
holder.bind(albums[position])
holder.binding.itemLockerAlbumMoreIv.setOnClickListener {
mItemClickListener.onRemoveSong(albums[position].id)
removeSong(position)
}
}
override fun getItemCount(): Int = albums.size
@SuppressLint("NotifyDataSetChanged")
fun addAlbums(albums: ArrayList<Album>) {
this.albums.clear()
this.albums.addAll(albums)
notifyDataSetChanged()
}
fun removeSong(position: Int){
albums.removeAt(position)
notifyDataSetChanged()
}
inner class ViewHolder(val binding: ItemLockerAlbumBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(album: Album){
binding.itemLockerAlbumCoverImgIv.setImageResource(album.coverImage!!)
binding.itemLockerAlbumTitleTv.text = album.title
binding.itemLockerAlbumSingerTv.text = album.singer
}
}
}
⑥ fragment_saved_album.xml에 아래의 내용을 입력한다.
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/locker_savedSong_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_locker_album"
android:overScrollMode="never"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>
⑦ SavedAlbumFragment에 아래의 내용을 입력한다.
class SavedAlbumFragment : Fragment() {
lateinit var binding: FragmentSavedAlbumBinding
lateinit var albumDB: SongDatabase
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentSavedAlbumBinding.inflate(inflater, container, false)
albumDB = SongDatabase.getInstance(requireContext())!!
return binding.root
}
override fun onStart() {
super.onStart()
initRecyclerview()
}
private fun initRecyclerview(){
binding.lockerSavedSongRecyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
val albumRVAdapter = SavedAlbumRVAdapter()
//리스너 객체 생성 및 전달
albumRVAdapter.setMyItemClickListener(object : SavedAlbumRVAdapter.MyItemClickListener{
override fun onRemoveSong(songId: Int) {
albumDB.albumDao().getLikedAlbums(getJwt())
}
})
binding.lockerSavedSongRecyclerView.adapter = albumRVAdapter
albumRVAdapter.addAlbums(albumDB.albumDao().getLikedAlbums(getJwt()) as ArrayList)
}
private fun getJwt() : Int {
val spf = activity?.getSharedPreferences("auth" , AppCompatActivity.MODE_PRIVATE)
val jwt = spf!!.getInt("jwt", 0)
Log.d("MAIN_ACT/GET_JWT", "jwt_token: $jwt")
return jwt
}
}
코드를 실행시켜보자. 좋아요 표시한 앨범이 각 유저별로 다르게 나타나는 것을 확인할 수 있을 것이다.
① drawable 디렉토리 하위로, selected_button.xml, not_selected_button.xml 파일을 추가한다.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="40dp" />
<solid android:color="@color/select_color" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="40dp" />
<solid android:color="@color/song_player_bg" />
</shape>
② fragment_look.xml 파일에 아래의 내용을 입력한다.
<?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=".LookFragment">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="둘러보기"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="20dp"
android:scrollbars="none"
app:layout_constraintTop_toBottomOf="@+id/textView2">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<android.widget.Button
android:id="@+id/look_chart_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="25dp"
android:background="@drawable/selected_button"
android:text="차트"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<android.widget.Button
android:id="@+id/look_video_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:background="@drawable/not_selected_button"
android:text="비디오"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<android.widget.Button
android:id="@+id/look_genre_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:background="@drawable/not_selected_button"
android:text="장르"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<android.widget.Button
android:id="@+id/look_situation_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:background="@drawable/not_selected_button"
android:text="상황"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<android.widget.Button
android:id="@+id/look_audio_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:background="@drawable/not_selected_button"
android:text="오디오"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
<android.widget.Button
android:id="@+id/look_atmostphere_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginRight="20dp"
android:layout_marginLeft="10dp"
android:background="@drawable/not_selected_button"
android:text="분위기"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</HorizontalScrollView>
<ScrollView
android:id="@+id/look_sv"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/horizontalScrollView">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/look_chart_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:text="차트"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="20dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@+id/look_chart_tv"
tools:layout_editor_absoluteX="20dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/look_chart_song_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:orientation="vertical"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/item_locker_album" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/look_video_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:text="비디오"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" />
<TextView
android:id="@+id/look_video_contents_tv"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_marginStart="20dp"
android:layout_marginVertical="20dp"
android:text="비디오 섹션"
android:gravity="center"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" />
<TextView
android:id="@+id/look_genre_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:text="장르"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_video_contents_tv" />
<TextView
android:id="@+id/look_genre_contents_tv"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_marginStart="20dp"
android:layout_marginVertical="20dp"
android:text="장르 섹션"
android:gravity="center"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_video_contents_tv" />
<TextView
android:id="@+id/look_situation_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:text="상황"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />
<TextView
android:id="@+id/look_situation_contents_tv"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_marginStart="20dp"
android:layout_marginVertical="20dp"
android:text="상황 섹션"
android:gravity="center"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />
<TextView
android:id="@+id/look_audio_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:text="오디오"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />
<TextView
android:id="@+id/look_audio_contents_tv"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_marginStart="20dp"
android:layout_marginVertical="20dp"
android:text="오디오 섹션"
android:gravity="center"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />
<TextView
android:id="@+id/look_atmostphere_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:text="분위기"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />
<TextView
android:id="@+id/look_atmostphere_contents_tv"
android:layout_width="match_parent"
android:layout_height="500dp"
android:layout_marginStart="20dp"
android:layout_marginVertical="20dp"
android:text="분위기 섹션"
android:gravity="center"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/look_genre_contents_tv" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
③ LookFragment에 아래의 내용을 입력한다.
class LookFragment : Fragment() {
lateinit var binding: FragmentLookBinding
private lateinit var songDB: SongDatabase
private lateinit var chartBtn : Button
private lateinit var videoBtn : Button
private lateinit var genreBtn : Button
private lateinit var situationBtn : Button
private lateinit var audioBtn : Button
private lateinit var atmosphereBtn : Button
private lateinit var buttonList: List<Button>
private lateinit var chartTv : TextView
private lateinit var videoTv : TextView
private lateinit var genreTv : TextView
private lateinit var situationTv : TextView
private lateinit var audioTv : TextView
private lateinit var atmosphereTv : TextView
private lateinit var textList: List<TextView>
lateinit var scrollView : ScrollView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentLookBinding.inflate(inflater, container, false)
songDB = SongDatabase.getInstance(requireContext())!!
// 스크롤 뷰 초기화
scrollView = binding.lookSv
// 버튼 초기화
chartBtn = binding.lookChartBtn
videoBtn = binding.lookVideoBtn
genreBtn = binding.lookGenreBtn
situationBtn = binding.lookSituationBtn
audioBtn = binding.lookAudioBtn
atmosphereBtn = binding.lookAtmostphereBtn
buttonList = listOf(chartBtn, videoBtn, genreBtn, situationBtn, audioBtn, atmosphereBtn)
// 텍스트 초기화
chartTv = binding.lookChartTv
videoTv = binding.lookVideoTv
genreTv = binding.lookGenreTv
situationTv = binding.lookSituationTv
audioTv = binding.lookAudioTv
atmosphereTv = binding.lookAtmostphereTv
textList = listOf(chartTv, videoTv, genreTv, situationTv, audioTv, atmosphereTv)
setButtonClickListeners()
return binding.root
}
override fun onStart() {
super.onStart()
initRecyclerview()
}
private fun initRecyclerview(){
val recyclerView = binding.lookChartSongRv
recyclerView.layoutManager = LinearLayoutManager(requireActivity())
val lookAlbumRVAdapter = LockerAlbumRVAdapter()
binding.lookChartSongRv.adapter = lookAlbumRVAdapter
lookAlbumRVAdapter.addSongs(songDB.songDao().getSongs() as ArrayList<Song>)
}
private fun setButtonClickListeners() {
for (i in buttonList.indices) {
val button = buttonList[i]
button.setOnClickListener {
initButton(i)
}
}
}
private fun initButton(idx : Int) {
for(presentBtn : Button in buttonList) {
if(presentBtn == buttonList[idx]) {
presentBtn.setBackgroundResource(R.drawable.selected_button)
} else {
presentBtn.setBackgroundResource(R.drawable.not_selected_button)
}
}
scrollView.smoothScrollTo(0, textList[idx].top)
}
}
이제 코드를 실행시켜보자. 클릭한 키워드에 따라 지정 스크롤이 잘 동작하는 것을 확인할 수 있다.