관리 메뉴

나만을 위한 블로그

[Android] TextWatcher를 공통 클래스로 만드는 방법 본문

Android

[Android] TextWatcher를 공통 클래스로 만드는 방법

참깨빵위에참깨빵_ 2022. 8. 31. 00:22
728x90
반응형

앱을 만들면서 editText를 다룰 때 제법 많이 사용하는 것이 TextWatcher라는 인터페이스다. 이것에 대한 설명은 아래 포스팅을 참고하거나 다른 블로그를 먼저 보고 오는 걸 추천한다.

 

https://onlyfor-me-blog.tistory.com/435

 

[Android] TextWatcher란?

앱을 만들다 보면 editText에 입력한 값을 실시간으로 관찰하면서 입력값에 따른 처리를 해야 할 때가 있다. 그 때 가볍게 써먹을 수 있는 편리한 TextWatcher란 인터페이스가 있다. 이름부터 뭘 하는

onlyfor-me-blog.tistory.com

 

TextWatcher는 결국 인터페이스기 때문에 구현할 경우 3개 함수를 모두 재정의해야 한다. 이 함수들을 액티비티 안에 구현하는 경우 코틀린을 사용하더라도 많은 공간을 차지하게 된다. 얼마나 차지하는지를 먼저 확인하기 위해 먼저 화면부터 대충 준비한다.

 

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginHorizontal="20dp"
        tools:context=".textwatcher.TextWatcherTestActivity">

        <EditText
            android:id="@+id/etFirst"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

        <EditText
            android:id="@+id/etSecond"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="60dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/etFirst"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.databinding.DataBindingUtil
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityTextWatcherTestBinding

class TextWatcherTestActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityTextWatcherTestBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_text_watcher_test)
        binding.run {
            lifecycleOwner = this@TextWatcherTestActivity

            etFirst.addTextChangedListener(object : TextWatcher {
                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                    TODO("Not yet implemented")
                }

                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                    TODO("Not yet implemented")
                }

                override fun afterTextChanged(p0: Editable?) {
                    TODO("Not yet implemented")
                }
            })

        }
    }
}

 

위의 코틀린 소스코드 기준으로 22~34번 줄까지 총 12줄을 TextWatcher 기본 구현만으로 잡아먹는다. 심지어 2개의 editText 중 하나에만 적용했는데도 이렇게 길다.

이 소스코드들이 editText가 있는 액티비티마다 존재한다면? 액티비티 / 프래그먼트 별 하는 작업은 다를 수 있지만 결국 함수 3개를 계속 반복구현하는 게 된다. 너무 귀찮다.

이 때 사용할 수 있는 방법이 TextWatcher를 구현하는 클래스를 만들고 주 생성자로 재정의 함수 3개에 각각 넣어줄 람다 함수를 선언하는 것이다. 이 방법은 아래 사이트에서 참고했다. 결론만 작성하고 앞단계는 건너뛰었으니 흥미있는 사람들은 한번 읽어보는 것도 좋겠다.

 

https://abangkis.medium.com/creating-dynamic-android-textwatcher-using-kotlin-fb5a4bc24996

 

Creating Dynamic Android TextWatcher using Kotlin

Today there’s 1 Pull Request that attract my attention. This code is part of a simple calculator activity. It listen to a field to…

abangkis.medium.com

 

아래 코드를 먼저 보자.

 

import android.text.Editable
import android.text.TextWatcher

class CommonTextWatcher(
    private val afterChanged: ((Editable?) -> Unit) = {},
    private val beforeChanged: ((CharSequence?, Int, Int, Int) -> Unit) = { _, _, _, _ -> },
    private val onChanged: ((CharSequence?, Int, Int, Int) -> Unit) = { _, _, _, _ -> }
): TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        beforeChanged(s, start, count, after)
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        onChanged(s, start, before, count)
    }

    override fun afterTextChanged(s: Editable?) {
        afterChanged(s)
    }
}

 

개인적으로 TextWatcher를 많이 써보진 않았지만 사용하더라도 저 3개 함수를 모두 재정의해야겠다 싶은 경우는 거의 없을 것이라고 생각된다. 만약 있다면 기획을 바꿔야 한다

그래서 원하는 함수만 사용할 수 있게 하기 위해 nullable한 람다 함수를 매개변수로 받도록 했고, 매개변수 별로 기본값을 설정해놓은 걸 볼 수 있다.

 

이제 남은 건 위에서 만든 CommonTextWatcher를 액티비티에서 사용하는 것 뿐이다.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.databinding.DataBindingUtil
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityTextWatcherTestBinding

class TextWatcherTestActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityTextWatcherTestBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_text_watcher_test)
        binding.run {
            lifecycleOwner = this@TextWatcherTestActivity

            etFirst.addTextChangedListener(CommonTextWatcher(
                onChanged = { source, _, _, _ ->
                    Log.e(TAG, "etFirst - onChanged source : $source")
                }
            ))

            etSecond.addTextChangedListener(CommonTextWatcher(
                afterChanged = { source ->
                    Log.e(TAG, "etSecond - afterChanged source : $source")
                }
            ))
        }
    }
}

 

아주 깔끔하게 변했다. 2개의 editText에 모두 TextWatcher를 적용했음에도 위 소스코드 기준 21~31번 줄까지 총 10줄을 차지한다. 하나의 editText가 12줄을 잡아먹던 이전 코드에 비하면 매우 간결해졌다.

각각의 editText에 입력되는 값을 로그로 출력해보면 아래와 같이 잘 찍혀나온다.

 

 

그런데 위의 소스코드처럼 로그만 찍는 경우는 아래처럼 더 줄일 수도 있다.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.databinding.DataBindingUtil
import com.example.kotlinprac.R
import com.example.kotlinprac.databinding.ActivityTextWatcherTestBinding

class TextWatcherTestActivity : AppCompatActivity() {

    private val TAG = this.javaClass.simpleName
    private lateinit var binding: ActivityTextWatcherTestBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_text_watcher_test)
        binding.run {
            lifecycleOwner = this@TextWatcherTestActivity
            
            val commonTextWatcher = CommonTextWatcher(
                afterChanged = { source ->
                    Log.e(TAG, "source : $source")
                }
            )

            etFirst.addTextChangedListener(commonTextWatcher)
            etSecond.addTextChangedListener(commonTextWatcher)
        }
    }
}

 

위 소스코드 기준 2개의 editText에 TextWatcher를 붙이는 것에 21~28번 줄까지 총 7줄만 사용하는 걸 볼 수 있다. 예시를 들기 위해 로그를 사용했지만 실제로는 저것보다 더 복잡한 함수나 코드들이 추가될 텐데, 그렇더라도 위의 방식을 응용한다면 소스코드가 훨씬 간결해질 것이다.

반응형
Comments