DataBindingComponent란?

DataBindingComponent는 BindingAdapter에 대한 getter를 포함하는 인터페이스다.  DataBindingComponent를 구현하는 클래스는 반드시 하나이상의 메서드를 가져야한다. 메서드의 이름은 접두어 get과 @BindingAdapter 메서드를 포함하는 클래스 또는 인터페이스의 이름의 합성어여야한다.

예를들어 @BindingAdapter 메서드를 가지고 있는 클래스의 이름이 ClickBinding이라면 DataBindingComponent구현체가 갖는 메서드의 이름은  getClickBinding()이 된다.

다음 예제 코드들은 데이터바인딩 클래스를 초기화 할 때 주로 사용되는 메서드이다. 

DataBindingUtil.setContentView(activity, layoutId, bindingComponent)
DataBindingUtil.inflate(..., bindingComponent)
DataBindingUtil.bind(view, bindingComponent)

메서드의 마지막 매개변수로 데이터바인딩 컴포넌트를 가질수 있다.

언제 쓰일까?

데이터바인딩 라이브러리에서는 바인딩 어댑터를 통해 사용자 정의 로직을 수행할 수 있다. 일반적으로 다음과 같이 쓰인다.

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
  view.setPadding(padding,
                  view.getPaddingTop(),
                  view.getPaddingRight(),
                  view.getPaddingBottom());
}

이러한 코드는 아무문제 없이 잘 동작하지만, static 메서드라는 특징을 가지고 있다. 그렇기 때문에 상태를 저장하거나,  close(), clear(), dispose() 와 같은 메소드를 호출하여 리소스 정리를 하는데 어려움이 있다.

데이터 바인딩 컴포넌트(DataBindingComponent)는 이러한 문제점을 해결할 수 있도록 도와준다.

그 외에도 static 메서드를 제거하고 동적으로 바인딩 어댑터 메서드를 바인딩 클래스에 포함시킬 수 있다.

주의 사항

하나의 모듈내에서 DatabindingComponent 인터페이스를 상속하거나 구현한 모든 인터페이스 및 클래스가 가지고 있는 getter 메서드들은 컴파일타임에 병합되기 때문에 DatabindingComponent 사용시 이를 유의하여 모든 메서드를 구현할 수 있도록 해야한다. 그렇지 않으면 컴파일타임에 에러가 발생한다.

중복 클릭 방지 바인딩 어댑터 만들기

일정시간내에 중복클릭 이벤트 발생시 첫번째 이벤트만 처리하고 나머지는 무시하는 바인딩 어댑터를 만들어보자.

인터페이스 또는 클래스로 바인딩 어댑터 메서드를 포함하는 파일을 만들자. 다음예제는 ClickBinding 인터페이스에 클릭 이벤트를 다룰 바인딩 어댑터 메서드를 정의했다.

public interface ClickBinding {
    @BindingAdapter("onClick")
    void setOnClickListener(View view, View.OnClickListener onClickListener);
}

ClickBinding의 구현체는 다음과 같다.

public class ClickBindingImpl implements ClickBinding, LifecycleObserver {

    private final PublishSubject<Pair<View, View.OnClickListener>> publishSubject = PublishSubject.create();
    private CompositeDisposable disposables = new CompositeDisposable();
    private final int TIME_OUT = 1000;

    public ClickBindingImpl(Lifecycle lifecycle) {
        lifecycle.addObserver(this);
    }

    @Override
    public void setOnClickListener(View view, View.OnClickListener onClickListener) {
        view.setOnClickListener(v -> publishSubject.onNext(Pair.create(view, onClickListener)));
        disposables.add(publishSubject.throttleFirst(TIME_OUT, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
                .subscribe(pair -> {
                    if (pair != null && pair.first != null && pair.second != null) {
                        pair.second.onClick(pair.first);
                    }
                })
        );
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroyed(){
        if(disposables.isDisposed()){
            disposables.dispose();
        }
    }
}

중복 클릭 이벤트를 방지하기 위해 RxJava의 throttleFirst를 사용한다. RxJava는 subcribe()를 호출로 반환되는 Disposable을 반드시 dispose()를 호출해서 리소스를 반환해야 한다. 그렇지 않으면 메모리 누수가 발생한다. 액티비티 또는 프레그먼트 같은 UI 컨트롤러가 파괴될 때 dispose하도록 하기 위해 LifecycleObserver를 구현했다.

RxBinding을 사용한다면 RxView.clicks(view).throttleFirst(…)로 대체해도 좋다.

이제 DataBindingComponent를 구현하자

public class ClickBindingComponent implements androidx.databinding.DataBindingComponent {

    private final ClickBindingImpl clickBinding;
    public ClickBindingComponent(Lifecycle lifecycle){
        clickBinding = new ClickBindingImpl(lifecycle);
    }

    //메소드이름 만드는 규칙에 유의 (예: get + 클래스 이름)
    public ClickBindingImpl getClickBinding() {
        return clickBinding;
    }
}

앞에서 언급했던 것과 같이 메서드 이름 만드는 규칙이 매우 엄격하므로 유의하자.

이제 액티비티에 버튼을 하나 만들고 바인딩 어댑터로 클릭 이벤트를 처리하는 예제를 살펴보자

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    public static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(
                this,
                R.layout.activity_main,
                new ClickBindingComponent(getLifecycle())
        );

        binding.setClickListener(this);

    }

    @Override
    public void onClick(View v) {
        Log.e(TAG, "Clicked");
        Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="clickListener"
            type="android.view.View.OnClickListener" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="click me"
            app:onClick="@{clickListener}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

 

실제로 클릭을 빠르게 반복해도 타임아웃을 1000ms로 지정했기 때문에 1초이내에 중복클릭되는 이벤트는 무시하게 된다.

앞의 예제는 https://github.com/Charlezz/DatabindingComponentSample 에서 확인 할 수 있다.

Dagger2를 사용하는 경우 개발자는 DataBindingComponent를 확장하고 확장한 인터페이스에 컴포넌트 어노테이션을 추가할 수 있다. Dagger2를 사용하고 있다면, Dagger2와 함께 DataBindingComponent를 사용하는 예제를 확인하자.

 

 

 

카테고리: 미분류

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.