안드로이드/앱개발(Android)

(Android) 중고거래 앱

김염인 2022. 2. 12. 23:38

목차

  1. 인트로 (완성앱 & 구현 기능 소개)
  2. 메인 페이지 Tab UI 구성하기
  3. 상품 목록 페이지 UI 구성하기
  4. Firebase Realtime Database 를 활용하여 DB 구조 구상하기
  5. Firebase에서 상품 목록 가져와 보여주기
  6. Firebase Storage 를 이용하여 사진 업로드 추가하기
  7. 마이페이지 구현하기
  8. 채팅 리스트 구현하기
  9. 채팅 페이지 구현하기
  10. 어떤 것을 추가로 개발할 수 있을까?
  11. 마무리

결과화면

이 챕터를 통해 배우는 것

  • 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

HomeFragment

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

chatting Fragment

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

UserFragment

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

3) UserFragment User 로그인 로그아웃 기능을 간단히 구현해주었으며 로그인시 로그아웃으로 Text가 바뀌고 회원가입이 불가능.

 


2. 상품 목록 페이지 / 채팅 페이지 구성

HomeFragment

우측 하단의 + 버튼을 누르면 내가 원하는 제목과 가격을 입력하고 이미지까지 넣어줄 수 있다.

로그인 기능을 이용할 수 있게 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