Butter Knife 라이브러리 사용법

|

안드로이드 GUI 개발을 하다보면, 가장 귀찮은 것 중 하나가 화면에 컴포넌트를 하나 추가하는 것입니다.

예를 들어 화면에 버튼을 추가한다고 하면 다음과 같은 작업들을 거쳐야 합니다.

  • XML Layout에 버튼을 추가한다.
  • 해당 Activity(Fragment)에서 그 컴포넌트에 해당하는 변수를 추가한다. ex) private Button button;
  • findViewById를 통해 그 컴포넌트를 변수에 할당한다. ex) button = (Button)findViewById(R.id.button)
  • 그 버튼에 setOnClickListener를 통해 이벤트를 등록한다.

컴포넌트가 몇 개 안 될 경우는 큰 문제가 안되는데, 컴포넌트가 많아질수록 위 코드들은 감당이 안 될 정도로 많아지고 복잡해집니다. 이럴 때 ‘Butter Knife’ 라이브러리를 사용하면 코드량이 상당히 줄어듭니다. (사실, 이런 식으로 별거 아닌 코드를 Wrapping 하는 외부 라이브러리를 쓰는 걸 선호하진 않지만 Butter Knife는 생각보다 괜찮은 것 같아서 최근에 조금씩 사용을 해보고 있습니다.)

간단한 예제 코드로 알아보도록 하겠습니다. 더 자세한 내용은 공식 홈페이지를 참조하시면 됩니다. 버전에 따라 문법이 조금씩 바뀌고 있는데, 여기서는 현재 최신 버전인 8.5.1 기준으로 사용해보겠습니다.


gradle 세팅

dependencies {
    ...
    compile 'com.jakewharton:butterknife:8.5.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
    ...
}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/activity_main"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:orientation="vertical"
  tools:context="com.snowdeer.butterknife.sample.MainActivity">

  <TextView
    android:id="@+id/title"
    android:textStyle="bold"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Title"
    android:textSize="32sp" />

  <Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Click Me!" />

  <ImageView
    android:id="@+id/imageview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</LinearLayout>


strings.xml

<resources>
  <string name="app_name">ButterKnifeSample</string>
  <string name="title">Hello. Butter Knife</string>
</resources>


MainActivity.java

여기서 가장 중요한 부분은 onCreate() 함수 내의 다음 코드입니다.

ButterKnife.bind(this);

위 코드를 호출해주어야 각 View들이 바인딩(Binding)이 됩니다.


import android.graphics.drawable.Drawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.BindDrawable;
import butterknife.BindString;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {

  @BindView(R.id.title)
  TextView titleView;
  @BindView(R.id.imageview)
  ImageView imageView;
  @BindString(R.string.title)
  String title;
  @BindDrawable(R.mipmap.ic_launcher)
  Drawable drawable;

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

    ButterKnife.bind(this);
  }

  @OnClick(R.id.button)
  void onButtonClicked() {
    titleView.setText(title);
    imageView.setImageDrawable(drawable);
  }

  ;
}


Fragment 에서의 예제

public class FancyFragment extends Fragment {

  @BindView(R.id.button1)
  Button button1;
  @BindView(R.id.button2)
  Button button2;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    ButterKnife.bind(this, view);
    // TODO Use fields...
    return view;
  }
}


ListView 등에서 사용하는 Adapter 및 ViewHolder 예제

public class MyAdapter extends BaseAdapter {

  @Override
  public View getView(int position, View view, ViewGroup parent) {
    ViewHolder holder;
    if(view != null) {
      holder = (ViewHolder) view.getTag();
    } else {
      view = inflater.inflate(R.layout.whatever, parent, false);
      holder = new ViewHolder(view);
      view.setTag(holder);
    }

    holder.name.setText("John Doe");
    // etc...

    return view;
  }

  static class ViewHolder {

    @BindView(R.id.title)
    TextView name;
    @BindView(R.id.job_title)
    TextView jobTitle;

    public ViewHolder(View view) {
      ButterKnife.bind(this, view);
    }
  }
}


이벤트 등록 예제

@OnClick(R.id.submit)
public void submit(View view) {
  // TODO submit data to server...
}

@OnClick(R.id.submit)
public void submit() {
  // TODO submit data to server...
}

@OnClick(R.id.submit)
public void sayHi(Button button) {
  button.setText("Hello!");
}

@OnClick({R.id.door1, R.id.door2, R.id.door3})
public void pickDoor(DoorView door) {
  if(door.hasPrizeBehind()) {
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();
  } else {
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();
  }
}

Assets의 텍스트 파일 불러오기

|

Assets 폴더에 들어있는 텍스트 파일의 내용을 String으로 읽는 코드입니다.

Android Studio 기준으로 Assets 폴더는 /app/src/main/assets에 위치하며, 네비게이션 뷰를 Project로 설정한 다음 폴더를 추가하면 됩니다.

본 예제는 텍스트 파일을 읽어오는 코드이지만, 다른 파일들에도 적용할 수 있습니다.

예제 코드는 다음과 같습니다.


PolicyActivity.java

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Html;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.lnc.cuppadata.R;
import com.lnc.cuppadata.gui.account.RegistrationActivity;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class PolicyActivity extends AppCompatActivity {

  @BindView(R.id.policy_view)
  protected TextView policyView;

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

    ButterKnife.bind(this);

    loadPolicy();
  }

  @OnClick(R.id.ok)
  protected void onOkButtonPressed() {
    Intent intent = new Intent(PolicyActivity.this, RegistrationActivity.class);
    startActivity(intent);

    finish();
  }

  @OnClick(R.id.cancel)
  protected void onCancelButtonPressed() {
    finish();
  }

  private void loadPolicy() {
    try {
      String policy = readFromAssets("policy.txt");

      policyView.setText(Html.fromHtml(policy));
    } catch(Exception e) {
      e.printStackTrace();
    }
  }

  private String readFromAssets(String filename) throws Exception {
    BufferedReader reader = new BufferedReader(new InputStreamReader(getAssets().open(filename)));

    StringBuilder sb = new StringBuilder();
    String line = reader.readLine();
    while(line != null) {
      sb.append(line);
      line = reader.readLine();
    }
    reader.close();
    return sb.toString();
  }
}

App에 사용된 Signature 알기

|

App에 사용된 Signature를 획득하는 방법은 다음과 같습니다.


터미널 명령어

keytool -list -printcert -jarfile [파일 이름]


예를 들면, 파일 이름이 ‘app-release.apk’라고 하면 다음과 같이 입력하면 됩니다.

keytool -list -printcert -jarfile app-release.apk

image -fullwidth

안드로이드 SDK의 Path 설정

|

안드로이드 SDK의 Path 설정

Mac에서 터미널을 통해 ‘adb’ 명령어를 실행하고 싶을 때가 종종 있습니다. Windows에서는 Android SDK가 설치된 경로를 환경변수로 등록해놓으면 되는데, Mac에서도 방법은 비슷합니다. 다만, Mac에서 Android SDK가 설치되어 있는 폴더 위치를 몰라서 Path 등록을 못하는 경우가 종종 있습니다.


Mac에서 Android SDK가 설치되는 폴더

사용자가 임의로 폴더 위치를 수정하지 않았다면, 기본적으로 Android SDK는 다음 위치에 설치됩니다.

/Users/snowdeer/Library/Android

.bash_profile 수정

환경 변수를 등록하기 위해서 .bash_profile을 수정하면 됩니다. 터미널에서 nano를 이용해서 수정을 해도 되고, 편하게 GUI 상의 ‘텍스트편집기’를 이용해서 수정을 해도 됩니다. 기본적으로 .bash_profile은 속성이 ‘숨김(Hidden)’으로 되어있습니다. Finder에서 숨김 파일을 보기 위해서는 Finder 창 안에서 단축키 Shift + Command + . 을 누르면 됩니다. .bash_profile/User/[사용자계정] 아래에 위치하고 있습니다.

그리고 .bash_profile 내에 다음과 같은 라인을 추가합니다.

export ANDROID_PATH=/Users/snowdeer/Library/Android
export PATH=$PATH:$ANDROID_PATH/sdk/platform-tools

이제 터미널을 다시 시작한 후 터미널 콘솔 창에서 adb 명령어를 입력해보면 잘 실행되는 것을 확인할 수 있습니다.

Gradle 빌드 오류 ‘No matching client found for package name’ 해결법

|

Android Studio에서 Google Service를 사용하는 어플을 개발하다가 패키지 이름(Package Name)을 변경해야 할 경우가 생겼습니다. 그래서 리팩토링(Refactoring) 기능을 활용해서 패키지 이름을 바꾸었습니다. 혹시나 리팩토링 과정에서 생략되었을지도 모르는 manifest.xml 안의 내용과 build.gradle 안의 내용도 찾아서 수정하였습니다.

그런 다음, 빌드(Build)를 수행했는데, 다음과 같은 오류가 발생했습니다.

Error:Execution failed for task ':app:processDebugGoogleServices'.
>; No matching client found for package name 'com.example.exampleapp'


해결법

해결법은 다음과 같았습니다.

먼저 Android Studio의 네비게이션 Bar의 보기 방식을 Project로 바꿔줍니다. (기본은 Android로 되어 있습니다.)

image -fullwidth 그러면 ‘app’ 폴더 아래 다음 이미지와 같이 ‘google-service.json’ 파일이 보일 것입니다. image -fullwidth 이 파일의 내부에서

"client": [
  {
    "client_info": {
      "mobilesdk_app_id": "1:169851363875:android:33b601ddd12370e2",
      "android_client_info": {
        "package_name": "com.snowdeer.myexample"
      }
    },
    ...
  }
]

패키지 이름을 변경한 패키지 이름과 동일하게 맞추어 주면 됩니다. 이 파일 안에 패키지 이름이 등장하는 부분이 몇 군데 있는데, 전부 수정해주면 됩니다.