마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

mvp 예제코드가 이게맞는지 확인부탁드립니다

0 추천

글쓰기앞서 코틀린코드 양식이 없어 읽기힘드실수도있을것같아 미리 죄송합니다

 

하단의 LoginActivity.kt 의 코드를

class LoginActivity : AppCompatActivity(), View.OnClickListener {
    private lateinit var binding: ActivityLoginBinding
    val TAG: String = "로그"


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)


        binding.btnLogin.setOnClickListener(this)
        binding.btnSignup.setOnClickListener(this)

    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn_login -> {
                Log.d(TAG, "로그인버튼 클릭")
                val email = binding.tvEmail.text.toString()
                val password = binding.tvPassword.text.toString()
                Login(email, password)
            }
            R.id.btn_signup -> {
                Log.d(TAG, "회원가입버튼클릭")
                intent = Intent(this, SignupActivity::class.java)
                startActivity(intent)
            }

        }
    }


    /***
     * 기능함수
     */





    fun Login(email: String, password: String) {
        var auth = FirebaseAuth.getInstance()
        if (email.isNotEmpty() && password.isNotEmpty()) {
            auth?.signInWithEmailAndPassword(email, password)
                ?.addOnCompleteListener { task ->
                    if (task.isSuccessful) {
                        MakeToast("로그인완료")
                        var user : UserModel = UserModel(email,password)
                        intent = Intent(this, MainActivity::class.java)
                        startActivity(intent)

                    } else {
                        MakeToast("아이디 또는 비밀번호가 일치하지않습니다")
                    }
                }
        } else {
            MakeToast("이메일과 비밀번호를 모두입렵해주세요")
        }
    }



    fun MakeToast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }
}


mvp패턴으로 바꿔보겠다고 다음과같이 나누었는데 이렇게바꾸는게 맞는지 확인부탁드립니다!

LoginActivity

class LoginActivity : AppCompatActivity(), View.OnClickListener,ViewInterface {
    private lateinit var binding: ActivityLoginBinding
    val TAG: String = "로그"
    var presenter = Presenter(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.btnLogin.setOnClickListener(this)
        binding.btnSignup.setOnClickListener(this)

    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn_login -> {
                intent = Intent(this, MainActivity::class.java)
                presenter.Login(binding.tvEmail.text.toString(),binding.tvPassword.text.toString(),intent)
            }
            R.id.btn_signup -> {
                intent = Intent(this, SignupActivity::class.java)
                presenter.startActivity(intent)
            }
        }
    }


    /***
     * 기능함수
     */
    override fun MakeToast(msg: String) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
    }

    override fun startActivity(intent: Intent) {
        startActivity(intent)
    }



}

Presenter

class Presenter(var viewInterface: ViewInterface) {
    lateinit var userModel: UserModel

    fun Login(email: String, password: String, intent: Intent) {
        if (email.isEmpty() || password.isEmpty()
        ) {
            viewInterface.MakeToast("아이디와 비밀번호를 모두 입력해주세요")

        } else {
            userModel = UserModel(email, password)
            var auth = FirebaseAuth.getInstance()
            auth?.signInWithEmailAndPassword(userModel.email, userModel.password)
                ?.addOnCompleteListener { task ->
                    if (task.isSuccessful) {
                        viewInterface.MakeToast("로그인 완료")
                        viewInterface.startActivity(intent)
                    } else {
                        viewInterface.MakeToast("아이디 또는 비밀번호가 틀렸습니다")
                    }
                }
        }

    }

    fun startActivity(intent: Intent) {
        viewInterface.startActivity(intent)
    }


}

ViewInterface

interface ViewInterface {

    fun MakeToast(msg:String)

    fun startActivity(intent: Intent)

}

 

원피스를찾은남자신현호 (170 포인트) 님이 2022년 11월 4일 질문

1개의 답변

0 추천
 
채택된 답변

네, 작성하신 코드의 방향이 맞습니다. 다만 한두가지 수정하시면 더 좋을 것 같은 부분을 말씀드릴게요.

class Presenter(var viewInterface: ViewInterface)

먼저 ViewInterface의 이름을 LoginView, Presenter 는 LoginPresenter와 같이 좀 더 명시적으로 주시면 어떤 역할을 하는 클래스들인지 이름을 보고도 알 수 있을 것 같습니다.

MakeToast -> makeToast
Login -> login

처럼 camelCase로 사용하시는 것이 대부분 사용하는 코틀린의 명명규칙입니다. 클래스이름, 명명 규칙을 먼저 말씀드리는 것은 그만큼 중요하기 때문입니다.

ViewInterface는 실제 구현은 LoginActivity가 되기 때문에 라이프사이클과 필요하다면 Confiruation changes를 고려해주실 수 있다면 더 좋을 것 같습니다. 즉 Configuration change 등으로 인해 ViewInterface는 null이 될 가능성이 있습니다. 따라서 nullable 타입이 되는 것이 더 안전합니다. 따라서 Activity onStart와 viewInterface를 설정해 주고 onStop에서 null로 설정해 주는 것이 더 안전합니다만, 해당 사항이 없다라고 확신이 드시면 그냥 현재대로 사용하셔도 됩니다. 이런 이유로 구글이 ViewModel이란 걸 도입했습니다만, 꼭 사용해야 한다고는 생각하지 않습니다. 프로젝트의 규모, 특성 등등 여러가지가 고려되여야할 부분이기 때문입니다.

그리고  가급적이면 안드로이 플랫폼에 종속적인 Context, Intent같은 클래스는 Presenter에서 사용하지 않는 것이 좋습니다. MVP 패턴이 사랑받는 이유 중의 하나가 Unit test 코드를 작성하기 아주 쉬운 구조를 제공하기 때문인데, 안드로이드에 종속된 코드가 들어가면 Unit test를 작성하기가 어려워 질 뿐더러 경우에 따라서는 lifecycle에 더 영향을 받는 코드가 될 수 있기 때문입니다. 그리고 startActivity같은 네비게이션 관련 코드는 코드의 주체가 뷰에 있는 것이 좀 더 자연스럽습니다. 따라서 아래와 같은 형태를 고려해 보시면 좋을 것 같습니다.

// LoginActivity
class LoginActivity : AppCompatActivity(), View.OnClickListener,ViewInterface {
    ...
 
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn_login -> {
                presenter.Login(binding.tvEmail.text.toString(),binding.tvPassword.text.toString())
            }
            R.id.btn_signup -> {
                intent = Intent(this, SignupActivity::class.java)
                startActivity(intent)
            }
        }
    }
 
    override fun navigateToMain() {
        startActivity(Intent(this, MainActivity::class.java))
    }
}
 
// LoginPresenter
fun Login(email: String, password: String, intent: Intent) {
        if (email.isEmpty() || password.isEmpty()) {
            viewInterface.showMessage("아이디와 비밀번호를 모두 입력해주세요")
            return
        } 
            
       requestLogin(email, password)
}

private val auth by lazy { FirebaseAuth.getInstance()!! }

private fun requestLogin(email: String, password: String) {
     auth.signInWithEmailAndPassword(email, password)
            .addOnCompleteListener { task ->
                    if (task.isSuccessful) {
                        viewInterface.showMessage("로그인 완료")
                        viewInterface.navigateToMain()
                        // 또는 위의 두 동작을 합쳐서
                        // viewInterface.loginSucceeded()
                    } else {
                        viewInterface.showMessage("아이디 또는 비밀번호가 틀렸습니다")
                        // 또는
                        // viewInterface.loginFailed(task.exception)
                    }
            }
}

 

만약 네비게이션이 여러개 라면 아래 처럼 enum 이나 seal class를 사용하시면 됩니다.
 

enum class NavDestination {
     LOGIN_TO_MAIN,
     
}


override fun navigate(destination: NavDestination) {
    val intent = when (destination) {
         LOGIN_TO_MAIN -> Intent(this, MainActivity::class.java)
     }
    startActivity(intent)
}

 

spark (227,510 포인트) 님이 2022년 11월 4일 답변
원피스를찾은남자신현호님이 2022년 11월 4일 채택됨
intent 부분이 계속 맘에걸렸는데 방향성을 제시해주셔서 너무감사합니다 ㅠㅠ
생명주기도 고려야할점까지 알려주신것도 정말 감사합니다!
클래스명은 그냥테스트용이라 대충지어봤습니다ㅎㅎ
참고로, 현재 파이어베이스에 로그인한 사용자는 Firebase.getInstance().getCurrentUser()로 접근할 수 있습니다.
...