티스토리 뷰

ButterKnife vs Data Binding


뷰바인딩을 위해 ButterKnife를 써오셨던 분들이 많을 것이다. 리플렉션을 사용하지 않아서 퍼포먼

스에 영향을 주지 않고 어노테이션 반으로 많은 보일러 플레이트 코드를 작성하지 않아도 도와주었던 안드로이드 개발자들의 친구였다. 


하지만 이제 그 자리를 Data Binding Library에게 내주어줄 때가 된 것 같다. 왜냐하면 Data Binding Library하나로 ButterKnife의 핵심 기능이었던 뷰바인딩은 기본이고 모델을 뷰에 바인딩 해줌으로써 훨씬 더 진보된 바인딩을 할 수 있기 때문이다. 그리고 어노테이션을 작성해 줄 필요도 없고 한번에 레이아웃을  바인딩 객체 안에 읽어 들임으로써, 모든 뷰에 접근을 할 수 있다. 그렇다고 퍼포먼스에 영향을 주는 것도 아니다. ButterKinfe 처럼 컴파일 타임에 필요한 처리를 다 함으로써 퍼포먼스를 희생할 필요가 전혀 없기 때문이다.


더우기 데이터 바인딩을 사용하면 쉽게 데이터 오브젝트와 뷰와의 의존성을 없앨 수 있기 때문에 MVP나 MVVM 같은 패턴의 작성이 한결 간편해진다. 즉, 단위테스트를 급격하게 향상시킬 수 있기 때문에 전반적인 생산성 향상 뿐만 아니라 앱의 품질 향상에도 기여를 하는 것이다.


그럼, ButterKnife와 Data Binding Library 비교해 봄으로써 데이터 바인딩의 파워풀한 모습을 하나씩 확인해 보도록 하자.


Activity Binding

 ButterKnife

어노테이션을 사용해서 아래와 같이 바인딩을 할 수 있다. findViewById를 일일이 호출할 필요없이 어노테이션과 ButterKnife.bind() 메소드를 한번 호출해 주면 된다.


class MyActivity extends Activity { @Bind(R.id.firstName) TextView firstName; @Bind(R.id.lastName) TextView lastName; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); } }



DataBinding

버터나이프가 액티비티에 어노테이션을 사용한다면, 데이터 바인딩은 레이아웃 파일과 액티비티 클래스에서 버터나이프 처럼 초기화를 해준다.


<layout> <LinearLayout> <TextView android:id="@+id/firstName"> <TextView android:id="@+id/lastName"> </LinearLayout> </layout>



데이터바인딩은 컴파잁타임에 레이아웃 파일에 기초하여 바인딩 클래스를 만들어준다. 이 클래스는 레이아웃 파일이름에서 언더스코어를 뺀 후 Binding이라는 접미어(suffix) 붙인 것과 같다. 예를 들면 레이아웃이 activity_my.xml이면 바이딩 클래스의 이름은 ActivityMyBinding이 된다.

예제 코드에서 액티비티의 setContentView 대신 데이터 버인딩의 setContentView() 메소드가 호출되고 있는 것을 눈여겨 보라. 그리고 binding.firstName, binding.lastName 을 호출하여 각 뷰에 접근할 수 있다.

class MyActivity extends Activity {

private ActivityMyBinding binding;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtils.setContentView(this, R.layout.activity_my);

} }



Fragment Binding

ButterKnife

프레그먼트에서의 바인딩도 액티비티와 아주 유사하다. onCreateView에서 ButterKinfe.bind() 메소드를 호출해준다.


public class MyFragment extends Fragment { @Bind(R.id.firstName) Button firstName; @Bind(R.id.lastName) Button lastName; @Override public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_my, parent, false); ButterKnife.bind(this, view); return view; } }



DataBinding

public class MyFragment extends Fragment {

private FragmentMyBinding binding;

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_fancy, container, false); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); binding = FragmentFancyBinding.bind(getView()); }

}




ListAdapter Binding

ButterKnife

ViewHolder  패턴을 사용해서 리스트어댑터를 사용하는 예제이다. holder를 contentView에 저장해 놓고 불러와서 코드를 통해 데이터 오브젝트를 바인딩 해준다.


public class MyAdapter extends BaseAdapter { @Override public View getView(int position, View contentView, ViewGroup parent) { ViewHolder viewHolder = null;

if (contentView != null) { viewHolder = (ViewHolder) view.getTag(); } else { view = inflater.inflate(R.layout.list_item, parent, false); viewHolder = new ViewHolder(view); contentView.setTag(viewHolder); }

User user = getItem(position); holder.firstName.setText(user.getFirstName()); holder.lastName.setText(user.getLastName()); return contentView; } public static class ViewHolder {
@Bind(R.id.firstName) TextView firstName; @Bind(R.id.lastName) TextView lastName; public ViewHolder(View view) { ButterKnife.bind(this, view); } } }



DataBinding

ViewHolder 클래스가 필요없다. 대신 아이템 레이아웃에 약간의 매핑 표현식이 필요하다. contentView의 tag에 ViewHolder 클래스 대신 아이템 바인딩 객체를 저장해 놓고 사용한다.  여기까지만 보면 별로 큰 차이가 없어 보이지만 데이터바인딩의 BindAdapter를 이용하면 레이아웃에 리스트뷰에 필요한 Collection 객체만 넘겨주는 걸로도 처리가 가능하다.


R.layout.list_item

<layout>

<data>

<variable name="user" type="com.example.User" />

</data> <LinearLayout> <TextView android:id="@+id/firstName"

android:text="@{user.firstName}"/>

<TextView android:id="@+id/lastName"

android:text ="@{user.lastName}"/>

</LinearLayout> </layout>




public class MyAdapter extends BaseAdapter { @Override public View getView(int position, View contentView, ViewGroup parent) { ListItemBinding binding = null;

if (contentView != null) { binding = (ListItemBinding) view.getTag();

convertView = binding.getRoot();

} else { binding = DataBindingUtil.inflate(inflater, R.layout.list_item, parent, false); contentView.setTag(binding); } binding.setUser(getItem(position)); return contentView; } }



Event Listener Binding

ButterKnife

submitButton에 클릭이벤트를 할당해주는 코이다. 한꺼번에 여러 개의  뷰에 같은 이벤트리스너를 바인딩할 수도 있다.

public class MyActivity extends Activity { private ActivityMyBinding binding; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); ButterKnife.bind(this); }

@OnClick(R.id.submitButton) public void onSubmitButtonClick(View view) { } }



DataBinding

레이아웃 파일에 이벤트리스너를 매핑해준다. 액티비티의 코드에 setActivity를 통해 바인딩을 해준다.


R.layout.activity_my

<layout> <data> <variable name="activity" type="com.example.MyActivity" /> </data> <LinearLayout> <Button android:onClick="@{activity.submitButtonClick}"> </LinearLayout> </layout>



public class MyActivity extends Activity { private ActivityMyBinding binding; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtils.setContentView(this, R.layout.activity_my); binding.setActivity(this); } public void onSubmitButtonClick(View view) { } }


데이터바인딩은 이보다 훨씬 더 진보된 이벤트리스너 바인딩을 지원한다. 예제의 경우, 다른 클래스에 있는 View.OnClickListener의 시그니쳐가 같은 메소드를 사용할 수도 있고,

<layout> <data> <variable name="handlers" type="com.example.EventHandlers" /> </data> <LinearLayout> <Button android:onClick="@{handlers::submitButtonClick}"> </LinearLayout> </layout>



Java 8에서 소개된 Lambda Expression을 이용하여 Event Listener와 시그니쳐가 다른 메소드도 사용이 가능한다.


<layout>
<data> <variable name="handlers" type="com.example.EventHandlers" />

<variable name="Task" type="com.example.service.SubmitTask" />

</data> <LinearLayout> <Button android:onClick="@{() -> handlers.submit(task)}">

</LinearLayout> </layout>


아래처럼 한 인터페이스에서 액티비티에 필요한 이벤트를 모아놓고 사용할 수 있어 좀더 깔끔한 프로그래밍 구조를 유지할 수 있다.

public interface EventHandlers { public void submitButtonClick(View view); public void anotherButtonClick(View view);

public void onTextChanged(CharSequence s, int start, int before, int count);

}



DataBinding's Other Useful Features

BindingAdapter

BindAdapter는 바인딩의 정적 메소드를 이용해 바인딩 해주는 기능이다. 이 기능을 잘 활용하면 ListView, RecyclerView 등도 간단한 레이아웃 바인딩으로 처리할 수 있고, 커스텀폰트, 리모트이미지 다운로드 등도 간단한 코드로 처리해 줄 수 있다.

Custom  Font

아마도 커스텀폰트를 사용해본 경험있다면, 텍스트뷰에 setTypeface()를 호출하여 일일이 세팅하거나, 폰트 라이브러리의 도움을 받거나, 아니면 TextView를 상속해서 커스텀폰트를 사용하는 커스텀뷰를 만들어 사용했을 것이다. 이 모든 방법이 본인이 원하던 코드는 아니었을 것이다. 하지만 데이터바인딩을 이런 문제를 상당부분 해결해 준다.  

<layout>

<data>

<variable name="FontUtils" type="com.example.FontUtils" />

</data> <LinearLayout> <TextView android:id="@+id/firstName"

app:font="@{FontUtils.CUTE_FONT}"/>

<TextView android:id="@+id/lastName"

app:font ="@{FontUtils.FANCY_FONT}"/>

</LinearLayout> </layout>


레이아웃의 커스텀속성인 font는 @BindingAdapter어노테이션이 있는 정적메소드를 찾아서어노테이션의 속성이 app:font이 메소드를 호출하게 된다. FontUtils의 코드를 약간 수정하면 Typeface를 객체를 매번 생성하지 않고 Singleton 으로 처리할 수 있다.

public class FontUtils {

public static final CUTE_FONT = "fonts/cutefont.ttf";

public static final FANCY_FONT = "fonts/fancyfont.ttf";

@BindingAdapter("app:font")

public static void setFont(TextView view, String fontName){

Typeface typeface = Typeface.createFromAssets(view.getContext().getAssets(), fontName)

      view.setTypeface(fontName);

  }

}



Image Binding

아마도 커스텀폰트를 사용해본 경험있다면, 텍스트뷰에 setTypeface()를 호출하여 일일이 세팅하거나, 폰트 라이브러리의 도움


<layout>

<data>

<variable name="user" type="com.example.User" />

</data> <LinearLayout> <ImageView android:id="@+id/photo"

app:image="@{user.photoUrl}"/>

</LinearLayout> </layout>



Picasso 라이브러리르 이용하면 리모트에 있는 이미지를 다운로드 하도록 레이아웃에 바인딩 할 수 있다. 


public class ViewBindingUtils {

@BindingAdapter("app:image")

public static void setImage(ImageView view, String url){

Picasso.with(context).load(url).into(view);

  }

}


 이 외에도 훨씬 풍부한 기능이 제공된다. 더 자세한 내용은 안드로이드 Data Binding Library 소개 페이지를 통해 참조하기 바란다. 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함