안드로이드(코틀린) 채팅앱 만들기 - UserInfoFragment 1

박준식·2022년 9월 5일
0

Catting

목록 보기
8/8
MainAcitivity의 3가지 탭 메뉴중 2번째인 UserInfoFragment는 타이틀과 세팅 버튼이 포함된 toolbar와 사용자 정보를 출력하는 Edit Text와 Edit Text의 내용으로 사용자 정보를 수정하는 버튼으로 구성된다. 프래그먼트가 생성될때 MainActivity에서 사용자 정보를 받아오고 이를 출력한다. 사용자 정보에 변경사항이 있으면 프래그먼트 재시작시 사용자 정보를 다시 받아온다. 사용자 정보는 Edit Text에 출력되고 변경하고 사용자 정보 수정 버튼을 누르면 NodeJs 서버에 변경 요청을 보내고 결과를 Dialog로 출력한다. 본문에서는 프래그먼트에서 Progress Bar를 Dialog로 출력하는 방법을 알아보고자 한다.

1. 전체 코드

  • fragment_user_info.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"
        android:background="@color/main_white"
        tools:context=".UserInfoFragment">
    
        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/main_white"
            android:minHeight="?attr/actionBarSize"
            android:theme="?attr/actionBarTheme"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:fontFamily="@font/bazzi"
                android:text="@string/main_user_info_title"
                android:textColor="@color/main_black"
                android:textSize="24sp"
                tools:layout_editor_absoluteX="16dp"
                tools:layout_editor_absoluteY="15dp" />
        </androidx.appcompat.widget.Toolbar>
    
        <ScrollView
            android:id="@+id/scrollView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/toolbar"
            app:layout_constraintVertical_bias="1.0">
    
            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <EditText
                    android:id="@+id/nickName"
                    android:layout_width="0dp"
                    android:layout_height="48dp"
                    android:layout_marginStart="16dp"
                    android:layout_marginTop="32dp"
                    android:layout_marginEnd="16dp"
                    android:background="@drawable/bg_edit_text"
                    android:ems="10"
                    android:fontFamily="@font/bazzi"
                    android:hint="nick name"
                    android:inputType="textEmailAddress"
                    android:padding="10dp"
                    android:textColor="@color/main_black"
                    android:textSize="24sp"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.0"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />
    
                <EditText
                    android:id="@+id/camID"
                    android:layout_width="0dp"
                    android:layout_height="48dp"
                    android:layout_marginTop="32dp"
                    android:background="@drawable/bg_edit_text"
                    android:ems="10"
                    android:fontFamily="@font/bazzi"
                    android:hint="cam ID"
                    android:inputType="textEmailAddress"
                    android:padding="10dp"
                    android:textColor="@color/main_black"
                    android:textSize="24sp"
                    app:layout_constraintEnd_toEndOf="@+id/nickName"
                    app:layout_constraintHorizontal_bias="0.0"
                    app:layout_constraintStart_toStartOf="@+id/nickName"
                    app:layout_constraintTop_toBottomOf="@+id/nickName" />
    
                <Button
                    android:id="@+id/updateUserInfoButton"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="32dp"
                    android:layout_marginBottom="32dp"
                    android:background="@drawable/bg_button"
                    android:fontFamily="@font/bazzi"
                    android:text="Update User Info"
                    android:textAllCaps="false"
                    android:textColor="@color/main_black"
                    android:textSize="24sp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="@+id/nickName"
                    app:layout_constraintHorizontal_bias="1.0"
                    app:layout_constraintStart_toStartOf="@+id/nickName"
                    app:layout_constraintTop_toBottomOf="@+id/camID" />
    
                <Button
                    android:id="@+id/addUserInfoButton"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="32dp"
                    android:layout_marginBottom="32dp"
                    android:background="@drawable/bg_button"
                    android:fontFamily="@font/bazzi"
                    android:text="Add User Info"
                    android:textAllCaps="false"
                    android:textColor="@color/main_black"
                    android:textSize="24sp"
                    android:visibility="invisible"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="@+id/nickName"
                    app:layout_constraintHorizontal_bias="1.0"
                    app:layout_constraintStart_toStartOf="@+id/nickName"
                    app:layout_constraintTop_toBottomOf="@+id/camID" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </ScrollView>
    
    
    </androidx.constraintlayout.widget.ConstraintLayout>
  • UserInfoFragment.kt
  • package com.example.catting
    
    import android.content.Context
    import android.content.Intent
    import android.graphics.BitmapFactory
    import android.os.Bundle
    import android.util.Base64
    import android.util.Log
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.Toast
    import androidx.activity.result.ActivityResultLauncher
    import androidx.activity.result.contract.ActivityResultContracts
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.view.isInvisible
    import androidx.core.view.isVisible
    import androidx.fragment.app.Fragment
    import androidx.recyclerview.widget.ItemTouchHelper
    import androidx.recyclerview.widget.LinearLayoutManager
    import androidx.recyclerview.widget.RecyclerView
    import com.example.catting.databinding.FragmentUserInfoBinding
    import com.google.gson.Gson
    import retrofit2.Call
    import retrofit2.Callback
    import retrofit2.Response
    
    
    class UserInfoFragment : Fragment() {
        lateinit var binding: FragmentUserInfoBinding
        lateinit var mainActivity: MainActivity
        lateinit var api:RetrofitApplication
    
        lateinit var userInfo: UserInfo
    
        override fun onAttach(context: Context) {
            super.onAttach(context)
            if(context is MainActivity) mainActivity = context
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            binding = FragmentUserInfoBinding.inflate(inflater, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            api = mainActivity.api
            userInfo = mainActivity.userInfo.copy()
    
            with(binding){
                toolbar.inflateMenu(R.menu.main_toolbar)
                toolbar.setOnMenuItemClickListener {
                    when(it.itemId){
                        R.id.setting_icon->{
                            mainActivity.openSettingActivity()
                            true
                        }
                        else -> false
                    }
                }
    
                updateUserInfoButton.setOnClickListener{
                    userInfo.nickName = nickName.text.toString()
                    userInfo.camID = camID.text.toString()
    
                    val dlg = ProgressBarDialog(mainActivity)
                    if(userInfo.nickName!!.isNotEmpty() && userInfo.camID!!.isNotEmpty()) {
                        dlg.show()
                        api.updateUserInfo(userInfo).enqueue(object : Callback<UserProfile> {
                            override fun onResponse(
                                call: Call<UserProfile>,
                                response: Response<UserProfile>
                            ) {
                                val userProfile = response.body()!!
                                if(userProfile.uid == "fail"){
                                    dlg.dismiss()
                                    Toast.makeText(mainActivity,"다시 시도해 주세요",Toast.LENGTH_SHORT).show()
                                }
                                else {
                                    mainActivity.userInfo = UserInfo(
                                        userProfile,
                                        mainActivity.userInfo.cats
                                    )
                                    MainActivity.isUserInfoFragmentNeedRefresh = true
                                    dlg.dismiss()
                                    mainActivity.binding.mainTab.selectTab(
                                        mainActivity.binding.mainTab.getTabAt(0)
                                    )
                                }
                            }
    
                            override fun onFailure(call: Call<UserProfile>, t: Throwable) {
                                Log.d("UserInfoFragment", t.message.toString())
                                Log.d("UserInfoFragment", "fail")
                                dlg.dismiss()
                            }
    
                        })
                    }
                    else{
                        Toast.makeText(mainActivity, "모든 정보를 입력해주세요", Toast.LENGTH_SHORT).show()
                    }
                }
    
                addUserInfoButton.setOnClickListener{
                    userInfo.nickName = nickName.text.toString()
                    userInfo.camID = camID.text.toString()
    
                    val dlg = ProgressBarDialog(mainActivity)
                    if(userInfo.nickName!!.isNotEmpty() && userInfo.camID!!.isNotEmpty()) {
                        dlg.show()
                        api.addUserInfo(userInfo).enqueue(object : Callback<UserProfile> {
                            override fun onResponse(
                                call: Call<UserProfile>,
                                response: Response<UserProfile>
                            ) {
                                val userProfile = response.body()!!
                                if(userProfile.uid == "fail"){
                                    dlg.dismiss()
                                    Toast.makeText(mainActivity,"다시 시도해 주세요",Toast.LENGTH_SHORT).show()
                                }
                                else {
                                    mainActivity.userInfo = UserInfo(userProfile)
                                    MainActivity.isUserInfoFragmentNeedRefresh = true
                                    dlg.dismiss()
                                    mainActivity.binding.mainTab.selectTab(
                                        mainActivity.binding.mainTab.getTabAt(2)
                                    )
                                }
                            }
    
                            override fun onFailure(call: Call<UserProfile>, t: Throwable) {
                                Log.d("UserInfoFragment", t.message.toString())
                                Log.d("UserInfoFragment", "fail")
                                dlg.dismiss()
                            }
    
                        })
                    }
                    else{
                        Toast.makeText(mainActivity, "모든 정보를 입력해주세요", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    
        override fun onResume() {
            super.onResume()
            // 프래그먼트 재시작
            if(mainActivity.userInfo.nickName == null && binding.addUserInfoButton.isInvisible){
                with(binding){
                    updateUserInfoButton.visibility = View.INVISIBLE
                    addUserInfoButton.visibility = View.VISIBLE
                }
            } else if (binding.addUserInfoButton.isVisible){
                with(binding){
                    updateUserInfoButton.visibility = View.VISIBLE
                    addUserInfoButton.visibility = View.INVISIBLE
                }
            }
    
            if(MainActivity.isUserInfoFragmentNeedRefresh){
                with(binding){
                    userInfo = mainActivity.userInfo.copy()
                    nickName.setText(userInfo.nickName)
                    camID.setText(userInfo.camID)
                }
                MainActivity.isUserInfoFragmentNeedRefresh = false
            }
            Log.d("UserInfoFragment","onResume")
        }
    }

2. 정리

  • Dialog로 Progress Bar 만들기
    • dialog_progress_bar.xml
    • <?xml version="1.0" encoding="utf-8"?>
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content">
      
          <ProgressBar
              android:id="@+id/progressBar"
              style="?android:attr/progressBarStyle"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_weight="1" />
      </LinearLayout>      
    • ProgressBarDialog.kt
    • package com.example.catting
      
      import android.app.Dialog
      import android.graphics.Color
      import android.graphics.drawable.ColorDrawable
      import android.view.Window
      import androidx.appcompat.app.AppCompatActivity
      import com.example.catting.databinding.DialogProgressBarBinding
      
      class ProgressBarDialog(private val context : AppCompatActivity) {
          private lateinit var binding : DialogProgressBarBinding
          private val dlg = Dialog(context)   //부모 액티비티의 context 가 들어감
      
          private lateinit var listener : MyDialogOKClickedListener
      
          fun show() {
              binding = DialogProgressBarBinding.inflate(context.layoutInflater)
      
              dlg.requestWindowFeature(Window.FEATURE_NO_TITLE)   //타이틀바 제거
              dlg.setContentView(binding.root)     //다이얼로그에 사용할 xml 파일을 불러옴
              dlg.setCancelable(false)    //다이얼로그의 바깥 화면을 눌렀을 때 다이얼로그가 닫히지 않도록 함
              dlg.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) //다이얼로그 배경 투명하게
              dlg.show()
          }
      
          fun dismiss(){
              dlg.dismiss()
          }
      
          interface MyDialogOKClickedListener {
              fun onOKClicked(content : String)
          }
      
      }
      
    • UserInfoFragment.kt
    •     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
              super.onViewCreated(view, savedInstanceState)
            
              ...
            
              with(binding){
            
            		...
            
              	val dlg = ProgressBarDialog(mainActivity)
      			if(userInfo.nickName!!.isNotEmpty() && userInfo.camID!!.isNotEmpty()) {
                      dlg.show()
                      api.updateUserInfo(userInfo).enqueue(object : Callback<UserProfile> {
                          override fun onResponse(
                              call: Call<UserProfile>,
                              response: Response<UserProfile>
                          ) {
                              val userProfile = response.body()!!
                              if(userProfile.uid == "fail"){
                                  dlg.dismiss()
                                  Toast.makeText(mainActivity,"다시 시도해 주세요",Toast.LENGTH_SHORT).show()
                              }
                              else {
                                  mainActivity.userInfo = UserInfo(
                                      userProfile,
                                      mainActivity.userInfo.cats
                                  )
                                  MainActivity.isUserInfoFragmentNeedRefresh = true
                                  dlg.dismiss()
                                  mainActivity.binding.mainTab.selectTab(
                                      mainActivity.binding.mainTab.getTabAt(0)
                                  )
                              }
                          }
      
                          override fun onFailure(call: Call<UserProfile>, t: Throwable) {
                              Log.d("UserInfoFragment", t.message.toString())
                              Log.d("UserInfoFragment", "fail")
                              dlg.dismiss()
                          }
      
                      })
                  }
                  else{
                      Toast.makeText(mainActivity, "모든 정보를 입력해주세요", Toast.LENGTH_SHORT).show()
                  }
            	
            	...
            
              }
          }
    1. 먼저 Dialog 클래스와 그의 화면을 담당할 xml 파일을 생성한다.
    2. Dialog 클래스에는 Dialog를 출력하는 show, 종료시키는 dismiss 함수가 필요하다.
    3. 원래 Dialog는 클릭한 버튼에 따른 반환값을 기반으로 다음 동작을 제어하지만 Progress Bar는 그럴 필요가 없으므로 MyDialogOKClickedListener를 사용하지는 않는다.
    4. Progress Bar는 다른 동작이 수행중 일때 인터페이스의 조작을 막기위해 사용하므로 서버와의 통신이 시작할때 show하고 종료되면 dismiss 시킨다. 또한 바깥 화면을 눌렀을 때 다이얼로그가 닫히지 않게 하고 다이얼로그 바깥은 투명하게 만들었다.

3. 주의사항

    • 에러코드
    • 발생경위
    • 원인
    • 해결방법

0개의 댓글