목차
- 인트로 (완성앱 & 구현 기능 소개)
- 메인 페이지 Tab UI 구성하기
- 상품 목록 페이지 UI 구성하기
- Firebase Realtime Database 를 활용하여 DB 구조 구상하기
- Firebase에서 상품 목록 가져와 보여주기
- Firebase Storage 를 이용하여 사진 업로드 추가하기
- 마이페이지 구현하기
- 채팅 리스트 구현하기
- 채팅 페이지 구현하기
- 어떤 것을 추가로 개발할 수 있을까?
- 마무리
결과화면



이 챕터를 통해 배우는 것
- RecyclerView 사용하기
- View Binding 사용하기
- Fragment 사용하기
- BottomNavigationView 사용하기
- Firebase Storage 사용하기
- Firebase Realtime Database 사용하기
- Firebase Authentication 사용하기
중고거래앱
Firebase Authentication 기능을 사용하여 로그인 회원가입 기능을 구현할 수 있음.
회원 기반으로 중고거래 아이템을 등록할 수 있음.
아이템 등록 시 사진 업로드를 위해 Firebase Storage 를 사용할 수 있음.
회원 기반으로 채팅 화면을 구현할 수 있음.
Fragment 를 사용하여 하단 탭 화면 구조를 구현할 수 있음.
FloatingActionButton 을 사용하기
중고거래 앱은 여러 기능을 같이 학습할 수 있는 유용한 앱이다. 특히 Firebase를 이용한 연동 방법은 혼자서 앱개발을 진행해야하는 입장에서 큰 배움이 됐고 RecyclerView, Fragment 활용 방법을 익힐 수있다.
1. Layout 구성 하기
중고거래 앱에서 구현해야할 Fragment는 총 세가지가 있다.
1) Home Fragment -> 중고거래 아이템이 등록 되는 Fragment

2) Chatting Fragment -> 중고 거래 구매자와 판매자가 채팅할 수 있는 chatFragment

3) User Fragment -> 로그인과 로그아웃이 가능한 Fragment

1) HomeFragment에 recyclerView를 넣어주어 등록된 중고아이템들을 보여준다.
2) ChatFragment에는 채팅방 리스트를 Fragment형식으로 넣어주고 아래와 같이 특정 아이템의 채팅방을 클릭하면 채팅한 목록이 뜰 수 있다.

3) UserFragment User 로그인 로그아웃 기능을 간단히 구현해주었으며 로그인시 로그아웃으로 Text가 바뀌고 회원가입이 불가능.
2. 상품 목록 페이지 / 채팅 페이지 구성

우측 하단의 + 버튼을 누르면 내가 원하는 제목과 가격을 입력하고 이미지까지 넣어줄 수 있다.
로그인 기능을 이용할 수 있게 Email 기능을 활성화 시켜준다.
Friebase DB와의 연동을 통해 진행 되는 사항이다. Firebase RealTimebase를 이용하여 아래와 같이 연동을 시켜준다.

Friebase Get 코드 구현
private lateinit var articleDB: DatabaseReference
private lateinit var userDB: DatabaseReference
private val auth: FirebaseAuth by lazy { Firebase.auth }
위에서 구성한 DB를 가져오기 위해서 DatabaseReference를 lateinit으로 받아준다.
Frirebase.auth를 이용하여 auth를 가져온다.
auth를 이용해 로그인한 상태인지 아닌지 알 수 있다.
if(auth.currentUser != null) {
// 로그인을 한상태
}
if (auth.currentUser?.uid != articleModel.sellerId){
// 내가 올리지 않은 아이템
1) firebase에서 프로젝트 생성
2) app수준의 build.gradle에 firebase 기능 추가,
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.gms.google-services'
}
dependencies {
implementation 'com.google.firebase:firebase-database-ktx'
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-storage-ktx'
}
3) 로그인 로그아웃을 위한 auth 선언 / RealtimeDatabase를 이용하기 위한 DatabaseReference 추가,
private lateinit var articleDB: DatabaseReference
private lateinit var userDB: DatabaseReference
private val auth: FirebaseAuth by lazy { Firebase.auth }
4) child Listener 생성
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// DB에 새로운 객체가 추가 되면 작동하는 Listener이ㅏ.
val articleModel = snapshot.getValue(ArticleModel::class.java)
articleModel ?: return
articleList.add(articleModel)
articleAdapter.submitList(articleList)
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onChildRemoved(snapshot: DataSnapshot) {}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {}
override fun onCancelled(error: DatabaseError) {}
}
5) DB를 불러옴
articleDB = Firebase.database.reference.child(DB_ARTICLES) // Articles라는 database.reference의 최상위에 바로 아래있는 userDB를 Articles를 가져온것,
userDB = Firebase.database.reference.child(DB_USERS) // Users라는 database.reference의 최상위에 바로 아래있는 users를 가져온것
// 위의 DB들은 처음에는 빈상태로 시작한다.
6) View에 그려줌
articleAdapter = ArticleAdapter(
onItemClicked = {articleModel ->
if(auth.currentUser != null){
// 로그인을 한상태
if (auth.currentUser?.uid != articleModel.sellerId){
// 내가 올리지 않은 아이템
val chatRoom = ChatListItem(
buyerId = auth.currentUser!!.uid,
sellerId = articleModel.sellerId,
itemTitle = articleModel.title,
key = System.currentTimeMillis()
)
// 내가 올리지 않은 아이템이면 chatRoom을 만들어준다.
userDB.child(auth.currentUser!!.uid)
.child(CHILD_CHAT) // 'chat' 라는 이름의 CHILD_CHAT을 child로 만들어줌
.push()
.setValue(chatRoom)
userDB.child(articleModel.sellerId)
.child(CHILD_CHAT)
.push()
.setValue(chatRoom)
Snackbar.make(view, "채팅방이 생성됐습니다. 채팅앱에서 확인해주세요,. ", Snackbar.LENGTH_SHORT).show()
}
else{
// 내가 올린 아이템
Snackbar.make(view, "내가 올린 물품 입니다. ", Snackbar.LENGTH_SHORT).show()
}
}
else{
Snackbar.make(view, "로그인 후 사용해주세요", Snackbar.LENGTH_SHORT).show()
}
articleDB.addChildEventListener(listener) // DB에 child 생성해준다.
7) 생명주기 등록
override fun onResume() {
super.onResume()
articleAdapter.notifyDataSetChanged()
}
override fun onDestroyView() {
super.onDestroyView()
articleDB.removeEventListener(listener)
}


위에는 만들어진 User Database와 회원가입된 유저목록이 실시간으로 반영된다.
3. 로그인 로그아웃 구현
package com.example.aop_part3_chaptor06.mypage
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import com.example.aop_part3_chaptor06.R
import com.example.aop_part3_chaptor06.databinding.FragmentMypageBinding
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class MyPafeFragment : Fragment(R.layout.fragment_mypage) {
private var binding : FragmentMypageBinding?=null
private val auth: FirebaseAuth by lazy {
Firebase.auth
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val fragmentMypageBinding = FragmentMypageBinding.bind(view)
binding = fragmentMypageBinding
fragmentMypageBinding.signInButton.setOnClickListener {
binding?.let { binding ->
val email = binding.emailEditText.text.toString()
val password = binding.passwordEditText.text.toString()
if(auth.currentUser == null){
// 로그인 과정
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()){ task ->
if (task.isSuccessful){
successSiginIn()
}
else{
Toast.makeText(context,"로그인에 실패했습니다. 이메일 또는 비밀번호를 확인해주세요", Toast.LENGTH_SHORT).show()
}
}
}
else{
auth.signOut()
binding.emailEditText.text.clear()
binding.emailEditText.isEnabled = true
binding.passwordEditText.text.clear()
binding.passwordEditText.isEnabled = true
binding.signInButton.text = "로그인"
binding.signInButton.isEnabled = true
binding.signUpButton.isEnabled = false
}
}
}
fragmentMypageBinding.signUpButton.setOnClickListener {
binding?.let { binding ->
val email = binding.emailEditText.text.toString()
val password = binding.passwordEditText.text.toString()
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()){
task ->
if (task.isSuccessful){
Toast.makeText(context, "회원가입에 성공하였습니다. 로그인버튼을 눌러주세요", Toast.LENGTH_SHORT).show()
}
else{
Toast.makeText(context, "회원가입에 실패하였습니다. 다시한번 확인해주세", Toast.LENGTH_SHORT).show()
}
}
}
}
fragmentMypageBinding.emailEditText.addTextChangedListener {
binding?.let { binding ->
val enable = binding.emailEditText.text.isNotEmpty() && binding.passwordEditText.text.isNotEmpty()
binding.signUpButton.isEnabled = enable
binding.signInButton.isEnabled = enable
}
}
fragmentMypageBinding.passwordEditText.addTextChangedListener {
binding?.let { binding ->
val enable = binding.emailEditText.text.isNotEmpty() && binding.passwordEditText.text.isNotEmpty()
binding.signUpButton.isEnabled = enable
binding.signInButton.isEnabled = enable
}
}
}
override fun onStart() {
super.onStart()
if(auth.currentUser == null){
//로그아웃일때
binding?.let{ binding ->
binding.emailEditText.text.clear()
binding.passwordEditText.text.clear()
binding.emailEditText.isEnabled = true
binding.passwordEditText.isEnabled = true
binding.signInButton.text = "로그인"
binding.signInButton.isEnabled = true
binding.signUpButton.isEnabled = false
}
}
else{
binding?.let{ binding ->
binding.emailEditText.setText(auth.currentUser!!.email)
binding.passwordEditText.setText("*********")
binding.emailEditText.isEnabled = false
binding.passwordEditText.isEnabled = false
binding.signInButton.text = "로그아웃"
binding.signInButton.isEnabled = true
binding.signUpButton.isEnabled = false
}
}
}
private fun successSiginIn() {
if (auth.currentUser == null){
Toast.makeText(context, "로그인에 실패 했습니다 다시 시도 해주세요", Toast.LENGTH_SHORT).show()
return
}
binding?.emailEditText?.isEnabled = false
binding?.passwordEditText?.isEnabled = false
binding?.signUpButton?.isEnabled = false
binding?.signInButton?.text = "로그아웃"
}
}
- 로그인 버튼 클릭시 / 로그아웃 버튼 클릭시 / 회원가입 버튼 클릭 시 와 같이 경우의수를 생각하며 구현을 한다.
package com.example.aop_part3_chaptor06.mypage
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import com.example.aop_part3_chaptor06.R
import com.example.aop_part3_chaptor06.databinding.FragmentMypageBinding
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
class MyPafeFragment : Fragment(R.layout.fragment_mypage) {
private var binding : FragmentMypageBinding?=null
private val auth: FirebaseAuth by lazy {
Firebase.auth
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val fragmentMypageBinding = FragmentMypageBinding.bind(view)
binding = fragmentMypageBinding
fragmentMypageBinding.signInButton.setOnClickListener {
binding?.let { binding ->
val email = binding.emailEditText.text.toString()
val password = binding.passwordEditText.text.toString()
if(auth.currentUser == null){
// 로그인 과정
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()){ task ->
if (task.isSuccessful){
successSiginIn()
}
else{
Toast.makeText(context,"로그인에 실패했습니다. 이메일 또는 비밀번호를 확인해주세요", Toast.LENGTH_SHORT).show()
}
}
}
else{
auth.signOut()
binding.emailEditText.text.clear()
binding.emailEditText.isEnabled = true
binding.passwordEditText.text.clear()
binding.passwordEditText.isEnabled = true
binding.signInButton.text = "로그인"
binding.signInButton.isEnabled = true
binding.signUpButton.isEnabled = false
}
}
}
fragmentMypageBinding.signUpButton.setOnClickListener {
binding?.let { binding ->
val email = binding.emailEditText.text.toString()
val password = binding.passwordEditText.text.toString()
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()){
task ->
if (task.isSuccessful){
Toast.makeText(context, "회원가입에 성공하였습니다. 로그인버튼을 눌러주세요", Toast.LENGTH_SHORT).show()
}
else{
Toast.makeText(context, "회원가입에 실패하였습니다. 다시한번 확인해주세", Toast.LENGTH_SHORT).show()
}
}
}
}
fragmentMypageBinding.emailEditText.addTextChangedListener {
binding?.let { binding ->
val enable = binding.emailEditText.text.isNotEmpty() && binding.passwordEditText.text.isNotEmpty()
binding.signUpButton.isEnabled = enable
binding.signInButton.isEnabled = enable
}
}
fragmentMypageBinding.passwordEditText.addTextChangedListener {
binding?.let { binding ->
val enable = binding.emailEditText.text.isNotEmpty() && binding.passwordEditText.text.isNotEmpty()
binding.signUpButton.isEnabled = enable
binding.signInButton.isEnabled = enable
}
}
}
override fun onStart() {
super.onStart()
if(auth.currentUser == null){
//로그아웃일때
binding?.let{ binding ->
binding.emailEditText.text.clear()
binding.passwordEditText.text.clear()
binding.emailEditText.isEnabled = true
binding.passwordEditText.isEnabled = true
binding.signInButton.text = "로그인"
binding.signInButton.isEnabled = true
binding.signUpButton.isEnabled = false
}
}
else{
binding?.let{ binding ->
binding.emailEditText.setText(auth.currentUser!!.email)
binding.passwordEditText.setText("*********")
binding.emailEditText.isEnabled = false
binding.passwordEditText.isEnabled = false
binding.signInButton.text = "로그아웃"
binding.signInButton.isEnabled = true
binding.signUpButton.isEnabled = false
}
}
}
private fun successSiginIn() {
if (auth.currentUser == null){
Toast.makeText(context, "로그인에 실패 했습니다 다시 시도 해주세요", Toast.LENGTH_SHORT).show()
return
}
binding?.emailEditText?.isEnabled = false
binding?.passwordEditText?.isEnabled = false
binding?.signUpButton?.isEnabled = false
binding?.signInButton?.text = "로그아웃"
}
}
최종 구현된 코드,

'안드로이드 > 앱개발(Android)' 카테고리의 다른 글
| (Android) 유튜브 앱 (0) | 2022.02.12 |
|---|---|
| (Android) 에어비엔비앱 (0) | 2022.02.12 |
| (Android) 틴더앱 (0) | 2022.02.12 |
| (코틀린 kotlin) 도서 리뷰 앱 (0) | 2022.02.12 |
| (코틀린 kotlin) 알람 앱 (0) | 2022.02.11 |