반응형

MVP를 적용할 상황을 설계


 

MVP를 적용할 간단한 예제를 구현하기 위해서, 다음과 같은 간단한 상황을 고려해보고자 한다.

 

(1) 이름과 이메일을 입력하면 이 값들이 저장되는 기능

(2) 하단 Text View에 저장된 값들이 출력되는 기능

(3) Application을 다시 시작할 때, 이미 저장된 값이 있으면 불러오는 기능

 

사실 이러한 간단한 수준의 기능은 하나의 Class에서 모두 해결할 수 있지만,

MVP 패턴에서는 어떻게 적용되는지 알아보기 위해서 모든 기능을 나누어 처리할 것이다.

 

  

 

LinearLayout을 이용한 간단한 레이아웃을 작성해보았다.

이에 대한 Code는 다음과 같다.

 

 
<!-- activity_main.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"
    android:gravity="center"
    android:paddingHorizontal="20dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_weight="4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <EditText
                android:id="@+id/edit_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="name"/>

            <EditText
                android:id="@+id/edit_email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="email"/>
        </LinearLayout>

        <Button
            android:layout_marginLeft="10dp"
            android:layout_weight="1"
            android:id="@+id/btn_save"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:text="SAVE"/>

    </LinearLayout>

    <TextView
        android:id="@+id/output_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="20dp"/>

    <TextView
        android:id="@+id/output_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
</LinearLayout>

 

 

 

 

MVP의 구성 요소 설계하기


 

위에서 MVP를 적용할 상황을 간단하게 설계해보았다.

그 다음으로 해야 할 일은 MVP를 이루는 구성 요소를 설계하는 것이다.

Model은 데이터의 처리, View는 데이터의 출력, Presenter는 MV의 중재자라는

Concept를 가지고 이에 대한 설계를 진행해보도록 하자.

 

 

 

 

즉, 사용자가 View(activity) 에 이름과 이메일을 입력하고 저장하면 다음과 같은 Logic이 발생할 것이다.

 

(1) View에서 Presenter로 요청이 넘어가고, 이 요청은 다시

(2) Presenter에서 Model로 넘어간다.

(3) Model에서 데이터를 내부 저장소에 저장하고, Presenter에 View의 갱신을 요청한다.

(4) Presenter에서는 View에 갱신될 데이터를 전달한다.

(5) View에서 갱신될 데이터를 전달받고 출력한다.

 

또한, Application이 새롭게 시작될 때 처리되는 Logic은 다음과 같다.

 

(1) View에서 Presenter로 데이터의 초기화에 대한 요청을 한다.

(2) Presenter에서는 이미 저장된 데이터가 있는지 Model에 요청한다.

(3) Model에서 저장된 데이터가 있는지 확인하고, 이를 Presenter로 전달한다.

(4) Presenter에서는 전달받은 데이터를 View에 넘겨준다.

(5) View에서는 전달받은 데이터가 있다면 View를 갱신하여, 저장된 데이터가 있음을 사용자에게 알린다.

 

이러한 기능을 아래의 실습에서 Code로 작성해보겠다.

 

 

 

 

 

Contractor 작성하기


 

View에서는 다음의 기능을 가져야 한다.

(1) 데이터를 입력 받아서, 이를 Activity에 출력하는 기능을 가져야 한다.

 

Presenter에서는 다음의 기능을 가져야 한다.

(1) Application이 시작될 때, 만약 저장된 데이터가 있다면 이를 가져온다.

(2) 데이터를 TextView에 출력할 수 있도록 View에게 데이터 출력을 요청한다.

(3) View의 EditText로부터 가져온 데이터를 Model에게 저장시킨다.

 

Contractor 인터페이스를 작성해서 이를 코드로 표현한다. 다음과 같이 작성된다.

 

// Contractor.kt

interface Contract {
    interface View {
        fun showInfo(info: JSONObject)  // Show info data at textview
    }

    interface Presenter {
        fun initInfo()  // when onCreate(), if saved data exist display it
        fun setInfo(info: JSONObject)   // Let view show info data at Textview
        fun saveInfo(info: JSONObject)  // Let model save info data from edittext
    }
}

 

 

 

 

 

Model 정의하기


 

 

// InfoDataSource.kt
 
interface InfoDataSource {
    interface LoadInfoCallback {
        fun onInfoLoaded(info: JSONObject)
        fun onDataNotAvailable()
    }
 
    fun getInfo(callback: LoadInfoCallback)
    fun saveInfo(info: JSONObject)
}
// InfoLocalDataSource.kt
 
class InfoLocalDataSource(context: Context) : InfoDataSource {
    private val sharedPreferences = context.getSharedPreferences("info", Context.MODE_PRIVATE)
    private val editor = sharedPreferences.edit()
 
    override fun getInfo(callback: InfoDataSource.LoadInfoCallback) {
        var info = sharedPreferences.getString("info", null)
        if(info != null) {
            callback.onInfoLoaded(JSONObject(info))
        } else {
            callback.onDataNotAvailable()
        }
    }
 
    override fun saveInfo(info: JSONObject) {
        editor.putString("info", info.toString())
        editor.commit()
    }
}
// InfoRepository.kt
 
class InfoRepository(context: Context) : InfoDataSource {
    private val infoLocalDataSource = InfoLocalDataSource(context)
 
    override fun getInfo(callback: InfoDataSource.LoadInfoCallback) {
        infoLocalDataSource.getInfo(callback)
    }
 
    override fun saveInfo(info: JSONObject) {
        infoLocalDataSource.saveInfo(info)
    }
}

 

 

 

 

 

Presenter 작성하기


 

// Presenter.kt
 
class Presenter(val view: Contract.View, val repository: InfoRepository) : Contract.Presenter {
    override fun initInfo() {
        repository.getInfo(object: InfoDataSource.LoadInfoCallback {
            override fun onInfoLoaded(info: JSONObject) {
                view.showInfo(info)
            }
            override fun onDataNotAvailable() {
                // Nothing
            }
        })
    }
 
    override fun setInfo(info: JSONObject) {
        view.showInfo(info)
        Log.d("KBW", info.toString())
    }
 
    override fun saveInfo(info: JSONObject) {
        repository.saveInfo(info)
    }
}

 

 

 

 

 

View 작성하기


 

// MainActivity.kt
 
class MainActivity : AppCompatActivity(), Contract.View {
    private lateinit var presenter: Presenter
    private lateinit var repository: InfoRepository
    private lateinit var binding: ActivityMainBinding
 
    override fun onCreate(savedInstanceState: Bundle?) {
        binding = ActivityMainBinding.inflate(layoutInflater)
 
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
 
        repository = InfoRepository(this)
        presenter = Presenter(this@MainActivity, repository)
 
        presenter.initInfo()
        initButtonListener()
    }
 
    override fun showInfo(info: JSONObject) {
        binding.outputName.text = info.getString("name")
        binding.outputEmail.text = info.getString("email")
 
        Log.d("KBW", info.getString("name"))
        Log.d("KBW", info.getString("email"))
    }
 
    fun initButtonListener() {
        binding.btnSave.setOnClickListener {
            Log.d("KBW", "hi")
 
            var info = JSONObject()
            info.put("name", binding.editName.text.toString())
            info.put("email", binding.editEmail.text.toString())
 
            binding.editName.text.clear()
            binding.editEmail.text.clear()
 
            presenter.setInfo(info)
            presenter.saveInfo(info)
        }
    }
}

 

 

 

반응형
복사했습니다!