Android Dialog & Realtime Database 사용하기

변현섭·2023년 8월 16일
0
post-thumbnail

1. 스플래시 화면 구성하기

앱이 시작될 때 자동으로 로그인이 진행될 수 있도록, 스플래시 화면에 비회원 로그인 로직을 추가해보자. 이를 위해 먼저 스플래시 화면을 구성해야 한다.

① scheduler라는 이름으로 프로젝트를 생성한다.

② default 패키지 하위로 SplashActivity를 생성한다.

③ 이전 포스팅의 내용을 참고하여 SplashActivity를 3초 후에 MainActivity로 전환하는 코드를 추가하고, Manifest 설정을 진행한다.
>> 스플래시 화면 구성하기

④ activity_splash.xml 파일을 아래와 같이 수정한다.

  • ConstraintLayout 컨테이너에 아래의 속성을 추가하여 배경색을 검은색으로 설정한다.
android:background="#000000"
  • TextView 태그를 추가한다.
<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="To Do List"
    android:textStyle="bold"
    android:gravity="center"
    android:textColor="#ffffff"
    android:textSize="40sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

2. Firebase 익명 로그인 적용하기

① 이전 포스팅의 내용을 참고하여 To do list라는 이름의 파이어베이스 프로젝트를 생성하고, 익명 로그인 토글을 ON으로 변경하는 것까지 진행한다.
>> 파이어베이스로 로그인 구현하기

② SplashActivity 파일에 아래의 코드를 추가한다.

  • auth.currentUser로 현재 로그인된 유저의 정보를 가져와 uid를 추출하고, MainActivity로 전환한다.
  • 만약 uid가 null이어서(로그인되어 있지 않아서) Null Pointer Exception이 발생할 경우, catch 블록에서 비회원 로그인을 진행한 후 MainActivity로 전환한다.
private lateinit var auth: FirebaseAuth

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_splash)
    auth = Firebase.auth
    try {
        Log.d("Splash", auth.currentUser!!.uid)
        Handler().postDelayed({
            startActivity(Intent(this, MainActivity::class.java))
            finish()
        }, 3000)
    } catch (e : Exception) {
        Log.d("Splash", "회원가입 필요")
        auth.signInAnonymously()
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    Toast.makeText(this, "비회원 로그인 성공", Toast.LENGTH_SHORT,).show()
                    Handler().postDelayed({
                        startActivity(Intent(this, MainActivity::class.java))
                        finish()
                    }, 3000)
                } else {
                    Toast.makeText(this, "비회원 로그인 실패", Toast.LENGTH_SHORT,).show()
                }
            }
    }
}

이제 애플리케이션을 구동만 하면, 자동으로 비회원 로그인이 처리된다.

3. MainActivity 레이아웃 구성하기

① 먼저 일정을 작성하기 위한 버튼으로 연필 아이콘을 drawable 패키지 안에 넣어주자.

  • 포토샵을 사용할 수 있으면 배경을 제거해주는게 좋다.

② activity_main.xml 파일에 아래의 내용을 추가한다.

  • 그림의 크기는 디자인 창에서 직접 조절한다.
  • Constraint Layout을 이용해 우하단에 연필 아이콘을 배치한다.
<ListView
	android:id="@+id/mainLV"
	android:layout_width="match_parent"
	android:layout_height="match_parent"/>
<ImageView
	android:id="@+id/pen"
	android:layout_width="88dp"
	android:layout_height="72dp"
	android:layout_margin="20dp"
	android:src="@drawable/pen"
	app:layout_constraintBottom_toBottomOf="parent"
	app:layout_constraintEnd_toEndOf="parent" />

③ 안드로이드 대화상자(Dialog)를 만들기 위해 layout 디렉토리 하위로 dialog라는 이름의 Layout Resource File을 추가한다.

④ dialog.xml 파일의 레이아웃을 LinearLayout으로 변경하고 orientation을 vertical로 변경한다.

android:orientation="vertical"

⑤ LinearLayout 컨테이너 안에 아래의 태그를 추가한다.

  • @android:color/transparent는 투명색을 지정하는 속성 값으로, EditText 태그에 기본적으로 생성되는 밑줄을 없애기 위해 사용하였다.
<EditText
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:layout_margin="20dp"
    android:hint=" 일정 입력하기"
    android:background="@android:color/transparent"/>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="0.5dp"
    android:layout_marginRight="10dp"
    android:layout_marginLeft="10dp"
    android:background="#999999"/>
<Button
	android:id="@+id/date"
    android:text="날짜 선택하기"
    android:layout_margin="20dp"
    android:layout_width="match_parent"
    android:layout_height="40dp"/>

4. Dialog 구성하기

① MainActivity 파일에 아래의 내용을 입력한다.

  • 대화상자가 잘 나오는지 코드를 실행하여 확인해보자.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val pen = findViewById<ImageView>(R.id.pen)
    pen.setOnClickListener {
        val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog, null)
        val builder = AlertDialog.Builder(this)
            .setView(dialogView)
            .setTitle("일정 메모")
        builder.show()
    }
}

② Dialog에서 findViewById로 버튼을 찾기 위해 builder.show()의 값을 alertDialog라는 변수에 할당한 후 클릭 이벤트 리스너를 등록한다.

  • 날짜 선택하기 버튼을 누르면, 날짜 선택 Dialog가 열린다.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val pen = findViewById<ImageView>(R.id.pen)
    pen.setOnClickListener {
        val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog, null)
        val builder = AlertDialog.Builder(this)
            .setView(dialogView)
            .setTitle("일정 메모")
        val alertDialog = builder.show()
        
        val dateBtn = alertDialog.findViewById<Button>(R.id.date)
		dateBtn?.setOnClickListener {
            val today = GregorianCalendar()
            val year: Int = today.get(Calendar.YEAR)
            val month: Int = today.get(Calendar.MONTH)
            val date: Int = today.get(Calendar.DATE)
            val dialog = DatePickerDialog(this, object : DatePickerDialog.OnDateSetListener {
                override fun onDateSet(
                    view: DatePicker?,
                    year: Int,
                    month: Int,
                    dayOfMonth: Int
                ) {
                    TODO("Not yet implemented")
                }
            }, year, month, date)
            dialog.show()
        }
    }
}

③ 이제 날짜를 선택했을 때의 이벤트를 처리해주어야 한다. 위 코드의 TODO("Not yet implemented") 부분에 아래의 내용을 입력한다.

  • 선택한 날짜를 버튼의 Text로 set 하는 방식으로 클릭 이벤트를 처리하자.
  • 참고로, DatePickerDialog에서 월을 나타내는 값은 0부터 시작하여 11까지의 범위를 가지기 때문에 month에는 1을 더해주어야 한다.
dateBtn.setText("${year} / ${month+1} / ${dayOfMonth}")

5. Firebase Realtime Database에 저장하기

① dialog.xml 파일에 저장하기 버튼을 추가하자. 날짜 선택 버튼 바로 아래에 작성하면 된다.

<Button
    android:id="@+id/save"
    android:text="저장하기"
    android:textSize="20sp"
    android:layout_margin="20dp"
    android:layout_width="match_parent"
    android:layout_height="50dp"/>

② 저장하기 버튼에 대한 클릭 이벤트 리스너를 등록한다.

val saveBtn = alertDialog.findViewById<Button>(R.id.save)
saveBtn?.setOnClickListener {

}

③ 파이어베이스의 프로젝트에 들어가 좌측 메뉴 > Realtime Database 버튼을 클릭한다.

④ 데이터베이스 만들기 버튼을 클릭한다.

⑤ 데이터베이스 위치는 미국으로 설정한다.

⑥ 테스트 모드에서 시작을 선택하고 사용 설정 버튼을 클릭한다.

⑦ 아래의 공식 문서를 참조하여 Realtime Database 관련 설정을 진행한다.
>> Realtime Database 관련 파이어베이스 공식 문서

  • gradle (Module) 파일의 dependencies에 아래의 내용을 추가한다.
implementation("com.google.firebase:firebase-database-ktx")
  • 클릭 이벤트 리스너에 아래의 내용을 추가하여 저장하기 버튼을 클릭했을 때, Realtime Database에 저장될 수 있도록 설정한다. 일단 파이어베이스 공식 문서의 코드를 그대로 가져와보자.
saveBtn?.setOnClickListener {
    val database = Firebase.database
    val myRef = database.getReference("message")
    myRef.setValue("Hello, World!")
}
  • Realtime Database에 값을 저장할 때, setValue를 쓰면 버튼을 여러번 눌러도 같은 값이면 한번만 저장하고, push().setValue를 쓰면 같은 값이어도 누를 때마다 저장된다.

⑧ 선택한 날짜를 저장할 수 있는 변수를 하나 선언하자.

  • 초기 값 설정을 위해서는 val이 아닌 var을 사용해야 한다. 저장하기 버튼의 클릭 이벤트 리스너 바로 위에 입력하면 된다.
var selectedDate = ""
  • 버튼의 Text를 set한 다음 selectedDate에 해당 날짜 데이터를 할당하면 된다.
dateBtn.setText("${year} / ${month+1} / ${dayOfMonth}")
selectedDate = "${year} / ${month+1} / ${dayOfMonth}"

⑨ 사용자가 입력한 일정을 저장하기 위해 dialog.xml 파일의 EditText 태그에 id 속성을 지정해주자.

android:id="@+id/schedule"

⑩ 이제 저장하기 버튼을 클릭했을 때, 일정과 날짜를 모두 저장할 수 있도록 클래스를 작성해야 한다.

  • 디폴트 패키지 하위로 Schedule이라는 이름의 Kotlin Class를 추가한다.
class Schedule (
    val date : String = "",
    val memo : String = ""
)

⑪ 다시 MainActivity로 돌아와서 저장하기 버튼에 대한 클릭 이벤트 리스너로 Schedule 타입 데이터가 저장되도록 만들어주자.

  • dismiss()는 Dialog를 닫는다는 뜻이다.
saveBtn?.setOnClickListener {
    val memo = alertDialog.findViewById<EditText>(R.id.schedule)?.text.toString()
    val database = Firebase.database
    val myRef = database.getReference("mySchedule")
    val schedule = Schedule(selectedDate, memo)
    myRef.push().setValue(schedule)
    alertDialog.dismiss()
}

⑫ 파이어베이스의 Realtime Database에 가서 결과를 확인해 볼 수 있다.

6. Firebase Realtime Database에서 값 가져오기

① MainActivity에 첫 화면에서 바로 일정표를 확인할 수 있도록 List View를 구성한다.

super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val database = Firebase.database
val myRef = database.getReference("mySchedule")
val listView = findViewById<ListView>(R.id.mainLV)

② ListView를 위한 Adapter 클래스를 생성한다.

class Adapter(val List : MutableList<Schedule>) : BaseAdapter() {
    override fun getCount(): Int {
        return List.size
    }

    override fun getItem(position: Int): Any {
        return List[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        
    }

}

③ listview_item이라는 이름으로 Layout Resource File을 layout 디렉토리 하위로 추가한다.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">
    
    <TextView
        android:id="@+id/ListViewDate"
        android:text="날짜"
        android:textSize="30sp"
        android:layout_margin="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/ListViewMemo"
        android:text="일정"
        android:textSize="15sp"
        android:layout_margin="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

④ Adapter 클래스의 getView 메서드를 정의해주자.

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
    var convertView = convertView
    if(convertView == null) {
        convertView = LayoutInflater.from(parent?.context).inflate(R.layout.listview_item, parent, false)
    }
    val date = convertView?.findViewById<TextView>(R.id.ListViewDate)
    val memo = convertView?.findViewById<TextView>(R.id.ListViewMemo)
    
    date!!.text = List[position].date
    memo!!.text = List[position].memo
    return convertView!!
}

⑤ MainActivity로 돌아와서 Adpater 클래스 관련 설정을 진행한다.

  • Adapter 클래스에 전달할 List를 onCreate 바로 위에 선언한다.
val schedule = mutableListOf<Schedule>()
  • ListView의 Adapter로 Adapter 클래스를 연결한다.
val adapter = Adapter(schedule)
listView.adapter = adapter
myRef.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        for(dataModel in snapshot.children) {
            schedule.add(dataModel.getValue(Schedule::class.java)!!)
        }
    }
    override fun onCancelled(error: DatabaseError) {
        TODO("Not yet implemented")
    }
})

⑥ 코드를 실행시켜보면, 아무 item도 ListView에 나타나지 않는다. 이는 파이어베이스의 비동기 처리와 연관이 있다. 그러므로 schedule List에 모든 값이 추가되면, Adapter를 새롭게 구성할 수 있도록 아래의 코드를 추가해야 한다.

  • for문이 완료되는 시점에 수행되어야 하므로 for문 바로 아래에 작성하면 된다.
adapter.notifyDataSetChanged()

⑦ onDataChange는 데이터베이스에 변경사항이 생길 때마다 콜백되는 메서드인데, for문을 사용하여 모든 데이터를 schedule List에 추가하다보니 데이터가 추가될 때마다, 기존 데이터가 함께 List에 추가되는 문제가 발생한다.

  • 따라서 아래와 같이 기존 데이터를 모두 지운 후, for문으로 모든 데이터를 가져오는 방식을 사용해야 한다.
override fun onDataChange(snapshot: DataSnapshot) {
    schedule.clear()
    for(dataModel in snapshot.children) {
        schedule.add(dataModel.getValue(Schedule::class.java)!!)
    }
    adapter.notifyDataSetChanged()
}

7. Persoanl Scheduler로 변경하기

지금까지 만든 scheduler는 모든 유저가 공유하는 형태이다. 이를 personal scheduler로 만들기 위해서는 사용자의 UID 값을 사용해 데이터를 저장하고 불러오는 로직을 추가해야 한다.

1) UID를 이용해 데이터 저장하기

① saveBtn의 클릭 이벤트 리스너의 myRef 값을 아래와 같이 변경한다.

val myRef = database.getReference("mySchedule").child(Firebase.auth.currentUser!!.uid)
  • 현재 사용자의 UID 값을 "mySchedule" 경로 하위의 자식 경로로 추가한다.

② 파이어베이스에서 결과를 확인해보자.

  • 기존에는 데이터가 저장되는 위치(key)와 Schedule 데이터(value) 쌍의 형태로 저장이 되었다.
  • 이제는 데이터가 저장되는 위치 하위에 사용자의 UID(key)와 Schedule 데이터(value) 쌍이 저장된다.

2) UID를 이용해 데이터 불러오기

① 데이터를 불러올 때도 마찬가지로 UID를 이용해 불러와야 한다. myRef의 addValueEventListener 부분을 아래와 같이 수정하자.

myRef.child(Firebase.auth.currentUser!!.uid).addValueEventListener(object : ValueEventListener {

② 안드로이드 Emulator에서 결과를 확인할 수 있다.

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글