티스토리 뷰

문제풀이에 앞서 아키텍쳐에 관해 잠깐 이야기를 하자면 

 

 

 

ui와 모델을 분리하여 결합도를 낮춰 유지보수를 용이하게 하기 위해 model, view, view model로 구조를 나누는데요

view는 ui 즉, 화면을

viewModel은 view와 model을 분리시켜 데이터를 처리하고 주고 받을 수 있게 하는 다리 역할을,

추가로 데이터베이스나 네트워킹을 위한  repository로 구분할 수 있습니다.

 

ViewModel과 View 사이의 데이터 변화를 관찰하기 위해 LiveData를 사용하는데요, 최근에는 Coroutine과 Flow를 활용하여 데이터를 처리하는 방식도 많이 사용되고 있습니다.

 

하지만 오늘은 Live Data를 사용할 것이기 때문에 이에 관련된 이야기를 조금 더 하자면 

 

1. View -  사용자 입력

2. View ➡️ ViewModel - 뷰 모델에 선언된 함수를 사용하여 값을 넘김

3. ViewModel - live data 값 변경

4. View - observer가 live data의 값이 변경되면 자동으로 변경된 값을 가져옴. 

 

이렇게 처리가 됩니다!

 

문제를 해결하기 위해 요구사항을 살펴보자면

화면을 크게 두 가지로 구성이 되어 있는데요

- Main

사용자 프로필 정보를 띄우는 곳(이름, 이메일)

편집 화면 버튼 클릭 시 수정하는 페이지로 넘어갑니다.

 

-프로필 편집 화면

사용자의 입력값을 받아 뷰 모델로 넘기는 곳입니다. 이전 값이 있다면 에디트텍스트에 띄웁니다.

이름, 이메일 모두 입력해야 하며, 하나라도 입력값이 없다면 토스트메세지를 띄워 경고합니다.

 

0123

 

UserProfileViewModel.kt

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class UserProfileViewModel: ViewModel() {
    private val _userName = MutableLiveData<String>()
    val userName : LiveData<String> get() = _userName
    //실무에선 보통 이렇게 캡슐화 해서 사용함.

    private val _userEmail = MutableLiveData<String>()
    val userEmail: LiveData<String> get() = _userEmail

    fun saveProfile(name: String, email: String){
        _userName.value = name
        _userEmail.value = email
    }
}

원래 바로 val username 을 하려고 했는데, 실무에서는 이렇게 캡슐화하여 외부에서 접근하지 못하게 한다고 하더라고요!

과제로 하는 것이긴 하지만 미리 이런 식으로 처리하는 게 좋을듯하여 해보았습니다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".homework.MainActivity"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/editViewBtn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="편집 화면"
            android:layout_marginRight="5dp"
            />
        <Button
            android:id="@+id/mainViewBtn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="표시 화면"
            android:layout_marginLeft="5dp"
            />
    </LinearLayout>
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="프로필 정보"
        android:textSize="27sp"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="16dp"/>
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="이름"
        android:textColor="@color/black"
        android:layout_marginTop="22dp"
        android:layout_marginLeft="16dp"/>
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="이름"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_marginLeft="16dp"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="이메일"
        android:textColor="@color/black"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"/>
    <TextView
        android:id="@+id/tv_email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="a@a.com"
        android:textColor="@color/black"
        android:textStyle="bold"
        android:layout_marginLeft="16dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="편집 화면에서 저장한 데이터가 \n자동으로 여기에 반영됩니다!"
        android:layout_gravity="center"
        android:layout_marginTop="22dp"/>

</LinearLayout>

 

간단한 화면이기 때문에 리니어로 처리했습니다.

타이틀 밑에 프래그먼트 콘테이너를 두어 편집화면 버튼 클릭 시 콘테이너가 표출되도록 처리했습니다.

 

MainActivity.kt

import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Observer
import com.example.ch3.R
import com.example.ch3.databinding.ActivityMain2Binding

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMain2Binding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityMain2Binding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        binding.editViewBtn.setOnClickListener {
        	//클릭 시 편집 프래그먼트 표시
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, EditProfileFragment())
                .commit()
            binding.container.visibility = View.VISIBLE
            binding.title.text = "프로필 편집"
        }
        binding.mainViewBtn.setOnClickListener {
            binding.container.visibility = View.GONE
            binding.title.text = "프로필 정보"
        }

        val viewModle : UserProfileViewModel by viewModels()
        //옵저버로 livedata 변경 시 자동으로 해당 값 표출
        viewModle.userName.observe(this, object : Observer<String>{
            override fun onChanged(value: String) {
                binding.tvName.text = value
            }
        })
        viewModle.userEmail.observe(this, object : Observer<String>{
            override fun onChanged(value: String) {
                binding.tvEmail.text = value
            }
        })
    }
}

 

 

fragment_edit_profile.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="이름"
        android:textColor="@color/black"
        android:layout_marginTop="22dp"
        android:layout_marginLeft="16dp"/>
    <EditText
        android:id="@+id/ed_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="이름"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="이메일"
        android:textColor="@color/black"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"/>
    <EditText
        android:id="@+id/ed_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textEmailAddress"
        android:hint="email"
        android:layout_marginRight="16dp"
        android:layout_marginLeft="16dp"/>
    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="저장"/>

</LinearLayout>

 

 

EditProfileFragment.kt

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.example.ch3.databinding.FragmentEditProfileBinding

class EditProfileFragment: Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = FragmentEditProfileBinding.inflate(layoutInflater)

		//activity의 viewModel 사용
        val viewModel: UserProfileViewModel by activityViewModels()

		//이미 값이 있다면 띄우기
        viewModel.userName.observe(viewLifecycleOwner){
            binding.edName.setText(it)
        }
        viewModel.userEmail.observe(viewLifecycleOwner){
            binding.edEmail.setText(it)
        }

        binding.btnSave.setOnClickListener {
            val name = binding.edName.text.toString()
            val email = binding.edEmail.text.toString()
            if (name.isEmpty() || email.isEmpty()){
                Toast.makeText(activity, "필수 입력값입니다.", Toast.LENGTH_SHORT).show()
            }else{
                viewModel.saveProfile(name, email)
                Toast.makeText(activity, "저장이 완료되었습니다.", Toast.LENGTH_SHORT).show()
            }
        }

        return binding.root
    }
}

 

사용자가 이름과 이메일을 모두 입력했다면 뷰모델에 선언된 메소드를 불러와 값을 넘겼습니다.

 

끝!!