버터나이프(ButterKnife)를 사용한 효율적인 추상화

대부분 프로젝트에서 소스의 양은 복잡도와 밀접한 관계가 있다. 그래서 OOP 언어에서는 공통 로직의 추상화와 모듈화로 소스의 복잡도를 줄이는 방향으로 가이드하고 있다는 것을 알 수 있다. 자바언어는 도메인에 특화된 여러 프레임웍이 제공하는 의존성 주입(DI)을 사용해서 코드의 복잡도를 줄이고 있다.

안드로이드 앱 프로젝트 또한 프레임웍 기반에서 개발한다. 그래서 안드로이드 프로젝트를 지원하는 각종 라이브러리가 존재하지만 프레임웍은 존재하지 않는 이유이기도 하다. 그래서 대부분의 의존성을 주입하는 라이브러리들은 어노테이션으로 컴파일 타임에 코드를 생성하는 형태이다. 이 형태는 런타임에 의존성 주입을 하는 형태보다 성능상의 이점이 있어서 표준처럼 사용하고 있다.

의존성 주입을 지원하는 대표 라이브러리로 버터나이프(ButterKnife)와 대거(Dagger)가 있다. 개인적으로도 버터나이프를 사용하고 있고, 안드로이드 프로젝트를 시작할 때에 처음 사용하는 라이브러리이다. 그래서 이 라이브러리를 사용해서, 비교적 좋은 형태의 추상화 예제를 살펴보겠다.

아래 예제는 안드로이드 프로젝트에서의 가장 많은 템플릿(Boilerplate) 코드로 뷰(View)에서 위젯(Widget)을 로드하는 예제이다. 아래 예제에서 액티비티(Activity)와 프레그먼트(Fragment)에서 어떻게 사용하고, 개선하는지 살펴보자.

1. 안드로이드 기본 예제

아래는 안드로이드 앱에서 많이 사용하는 액티비티와 프레그먼트의 예제이다.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */
public class MainActivity extends AppCompatActivity {

	private TextView tv01;
	private TextView tv02;
	private TextView tv03;
	private TextView tv04;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		tv01 = (TextView) findViewById(R.id.activity_main_tv_01);
		tv02 = (TextView) findViewById(R.id.activity_main_tv_02);
		tv03 = (TextView) findViewById(R.id.activity_main_tv_03);
		tv04 = (TextView) findViewById(R.id.activity_main_tv_04);
	}
}

액티비티 예제

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class MainFragment extends Fragment {

	private TextView tv01;
	private TextView tv02;
	private TextView tv03;
	private TextView tv04;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.fragment_main, parent, false);

		tv01 = (TextView)view.findViewById(R.id.fragment_exview_tv_01);
		tv02 = (TextView)view.findViewById(R.id.fragment_exview_tv_02);
		tv03 = (TextView)view.findViewById(R.id.fragment_exview_tv_03);
		tv04 = (TextView)view.findViewById(R.id.fragment_exview_tv_04);

		return view;
	}

}

프레그먼트 예제

위 예제들은 findViewId() 메서드를 사용해서 화면에 배치한 위젯을 로드하는 예제이다. 이 형태로 화면에서 많은 위젯을 로드하면, 코드의 양도 그만큼 늘어난다.

2. 버터나이프(Butterknife) 예제

버터나이프를 사용해서 액티비티나 프레그먼트에서 화면을 로드하는 예제이다.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import net.sjava.example.abstraction.R;

import butterknife.BindView;
import butterknife.ButterKnife;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeActivity extends AppCompatActivity {

	@BindView(R.id.activity_main_tv_01) TextView tv01;
	@BindView(R.id.activity_main_tv_02) TextView tv02;
	@BindView(R.id.activity_main_tv_03) TextView tv03;
	@BindView(R.id.activity_main_tv_04) TextView tv04;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ButterKnife.bind(this);
	}
}

액티비티 예제

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import net.sjava.example.abstraction.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeFragment extends Fragment {
	@BindView(R.id.fragment_exview_tv_01) TextView tv01;
	@BindView(R.id.fragment_exview_tv_02) TextView tv02;
	@BindView(R.id.fragment_exview_tv_03) TextView tv03;
	@BindView(R.id.fragment_exview_tv_04) TextView tv04;

	private Unbinder unbinder;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.fragment_main, parent, false);
		unbinder = ButterKnife.bind(this, view);
		return view;
	}

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		unbinder.unbind();
	}
}

프레그먼트 예제

버터나이프 예제들은 @BindView 어노테이션을 사용해서 화면의 위젯을 로드한다. 이 예로 위의 안드로이드 기본 예제보다 약간의 수고를 덜 수 있다.

3. 버터나이프 추상화 예제

버터나이프를 사용해서 액티비티와 프레그먼트에서 추상화하는 예제와 추상화한 액티비티와 프레그먼트를 사용하는 예를 살펴보자.

3.1 추상화 예제

import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v7.app.AppCompatActivity;

import butterknife.ButterKnife;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */
public abstract class AbsBaseActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		super.setContentView(getLayoutId());
		ButterKnife.bind(this);
	}

	@LayoutRes
	public abstract int getLayoutId();
}

추상화 액티비티 예제

이 추상화한 클래스는 abstract 클래스로 이 클래스를 상속하는 클래스는 getLayoutId() 메서드를 구현하게 만든다. 이 메서드에서는 액티비티의 화면인 xml 파일의 id를 반환하도록 한다.


import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public abstract class AbsBaseFragment extends Fragment {

	Unbinder mUnbinder;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		View view = inflater.inflate(getLayoutId(), container, false);
		mUnbinder = ButterKnife.bind(this, view);
		return view;
	}

	@LayoutRes
	public abstract int getLayoutId();

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		mUnbinder.unbind();
	}
}

추상화 프레그먼트 예제

이 추상화한 프레그먼트는 추상화한 액티비티의 getLayoutId()와 더불어, onDestroyView() 메서드에서 버터나이프로 읽어들인 뷰를 제거하는 메서드를 추가했다.

3.2 사용 예제

import android.os.Bundle;
import android.widget.TextView;
import net.sjava.example.abstraction.R;

import butterknife.BindView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeMainActivity extends AbsBaseActivity {

	@BindView(R.id.activity_main_tv_01) TextView tv01;
	@BindView(R.id.activity_main_tv_02) TextView tv02;
	@BindView(R.id.activity_main_tv_03) TextView tv03;
	@BindView(R.id.activity_main_tv_04) TextView tv04;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}

	@Override
	public int getLayoutId() {
		return R.layout.activity_main;
	}
}

액티비티 예제

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import net.sjava.example.abstraction.R;

import butterknife.BindView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeMainFragment extends Fragment {
	@BindView(R.id.fragment_exview_tv_01) TextView tv01;
	@BindView(R.id.fragment_exview_tv_02) TextView tv02;
	@BindView(R.id.fragment_exview_tv_03) TextView tv03;
	@BindView(R.id.fragment_exview_tv_04) TextView tv04;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		return super.onCreateView(inflater, parent, savedInstanceState);
	}
}

프레그먼트 예제

* 예제 코드 : https://github.com/mcsong/AbstractionExample
이 글에서 버터나이프를 사용해서 비교적 효율적으로 추상화해서 사용하는 방법을 살펴봤다.

답글 남기기

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