티스토리 뷰
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
Custom Font
<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>
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
<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 소개 페이지를 통해 참조하기 바란다.
'Android' 카테고리의 다른 글
DialogFragment handling from a Fragment using Result API (0) | 2022.02.17 |
---|---|
Android, how to use String Resource in an abstract way (1) | 2022.02.13 |
Fragment에서 ViewBinding 메모리누수 방지하기 (0) | 2021.07.30 |
빌드 환경에 따라 BaseURL 관리하기 (0) | 2020.09.26 |
DataBinding을 이용하여 MVVM 패턴 구현하기 (0) | 2016.10.03 |