Naver Tech Concert Day-1

개요

  • 행사명 : Naver Tech Concert

  • URL : http://techcon.naver.com/

  • 일정 : DAY 1- 2018년 11월 1일(목) 10:00 - 17:30

    DAY 2- 2018년 11월 2일(금) 10:00 - 17:30

  • 장소 : 정자동 네이버 그린팩토리 본사 2층 CONNECT HALL

  • 일정 :

    • 11월 1일(목) D AY 1
      09:30 ~ 09:55   참가 등록
      10:00 ~ 10:10   인사말
      10:10 ~ 11:10   Android DataBinding (기초에서 고급까지) 
      11:10 ~ 12:10   MVVM with Grab architecture 
      12:10 ~ 13:30   점심시간 (별도 제공은 없습니다.) 
      13:30 ~ 14:30   디자인 1도 모르는 개발자, UX디자인 시작하기
      14:30 ~ 15:30   Material Design의 철학과 적용 사례
      15:30 ~ 16:30   Android Kotlin을 통한 개발 전략 
      16:30 ~ 17:30   Obfuscation 101: 난독화, 프로가드, R8, 트랜스포머 API
      17:30   마무리
  • 11월 2일(금) DAY 2
       09:30 ~ 09:55  참가 등록
       10:00 ~ 11:00  변화의 시대: 안드로이드 앱 어떻게 개발할 것인가?
       11:00 ~ 12:00  Efficient and Testable MVVM pattern(with using AAC, Rx, Koin)
       12:00 ~ 13:30  점심시간 (별도 제공은 없습니다.)
       13:30 ~ 14:30  내가 사랑한 개발자들
       14:30 ~ 15:30  안드로이드 웹뷰의 모든것 
       15:30 ~ 16:30  안드로이드에서 코루틴은 어떻게 적용할 수 있을까?
       16:30 ~ 17:30  자동화, 계륵에 살 붙이기
       17:30  마무리 

현장


전리품

01. 인사말

900명이 넘는 사람이 신청했으나, 어쩔 수 없이 추천으로 초청하게 됨.

네이버 내부 사내 개발자 역량 강화를 위해 다양한 행사를 진행함.

외부 개발자 초빙하여 부정기적으로 다양한 주제의 테크톡을 진행함.

내부적으로는 주제별로 사내 밋업을 진행하며 가장 활발한 그룹이 자바스크립트 프런트엔드 개발, 안드로이드, iOS 그룹이 활발함.

사내 내외부 교류와 네트워킹을 위해 준비하며, 프런트엔드 그룹은 이미 진행하고 있으며, 이번에 안드로이드 그룹도 진행하게 됨.

안드로이드 단말 나온지 10년이 되는데 여러 노하우를 공유했으면 함.


02. Android DataBinding (기초에서 고급까지)

PRISM Live Studio 앱에서 데이터바인딩을 전면적으로 사용 중.

DataBinding이란?

XML(Screen) <- Java/Kotlin(Data/Logic) 인 부분을***Binding.java가 연결함.

이 부분은 런타임시가 아닌 XML 빌드시 생성됨.

XML에 기존대비 <Layout> 태그 추가 필요.

activity_main.xml -> ActivityMain.xml -> ActivityMainBinding.java

기존 레이아웃을 setContentView 사용하던 방식이 아니라 DataBindingUtil을 이용해 레이아웃을 통해 DataBinding 클래스(예. ActivityMainDataBinding)를 사용.

Activity, Fragment, View(ViewHolder) 등에서 사용 가능

XML을 통해 DataBinding 생성시 그 안의 View들에 대한 참조가 가능함.

Actvitiy 등에서 그 View들을 참조하여 사용이 가능하나 참조를 제거하는 방향으로 하고자 함.

XML에서 DataBinding 사용시 data 변수 추가 필요.

Gradle 3.2.0 미만에는 DataBinding 클래스 내에 구현이 있으나, 이 후부터는 DataBinding 클래스가 abstract 클래스이고 DataBindingImple 클래스에 구현이 들어감.

<layout>
    <data>
        <variable
                  name="model"
                  type="com.test.blabla.SampleModel" />
    </data>
    <LinearLayout
                  ... >
        <TextView
                  ...
                  android:text="@{model.title}" />
    </LinearLayout>
</layout>

데이터바인딩 이루어지는 규칙 (실행 우선 순위 순)

  • BindingAdapter (사용자 지정 Setter)
  • BindingMethod (이름이 바뀐 Setter)
  • Set Method (자동 Setter)

Setter 예제

<TextView
          android:id="@+id/title"
          ...
          android:enabled="@{true}" />

위와 같은 xml 빌드시 ***Binding.java가 아래처럼 생성됨.

this.title.setEnabled(true)

커스텀뷰의 추가된 메소드 등을 위와 같이 사용 가능.

BindingAdapter 예제

바인딩 어댑터를 사용시 애노테이션 사용하면 public static 필수임.

@BindingAdapter(value={"naver"})
public static void naverBindingAdapter(TextView, boolean isNaver) {
    ...
}

위 바인딩 어댑터를 사용하는 xml

<TextView
          android:id="@+id/title"
          ...
          app:naver="@{true}" />

위 xml 빌드시 생기는 ***Binding.java

SampleBindingAdapter.naverBindingAdater(this.title, true);

BindingAdapter 애노테이션 선언부

@Target(ElementType.METHOD)
public @interface BindingAdapter {
    String[] value();
    boolean requireAll() default true;
}
  • value : XML Attribute Name List, 다수 개일 경우 메소드 파라미터 순서와 매칭이 되어야 함.
  • requireAll : value()의 Attribute가 모두 존재할 때만 처리할지 여부. false일 경우 int/double/float/... 등의 타입의 Attribute가 없을 경우는 값은 0, Object 타입의 Attribute가 없을 경우는 null로 처리됨.

발표자께서는 requireAll=false 을 사용하였었으나, 다수 개의 Attribute에서 어떤 값만 옵셔널하게 처리가 안되어 아래와 같이 오버로딩하면서 쓰고 있음.

@BindingAdapter(value={"android:visibility", "animType", "animDuration"})
public static void newAnimationBindingMethod(View view, boolean visibility, @AnimType int animType, int animDuration) { ... }

@BindingAdapter(value={"android:visibility", "animType"})
public static void newAnimationBindingMethod(View view, boolean visibility, @AnimType int animType) { ... }

Observable

XML(Screen) <- ***Binding.java <- Java/Kotlin(Data/Logic)

Java/Kotlin 등에서 변경시 XML에 알려주길 위해 Observable 사용.

(DataBinding이 아니라면 LiveData해도 됨.)

Observable 인터페이스 있으나, 구글에서 사용하기 편하게 BaseObservable 제공함.

public static class SampleModel extends BaseObservalbe {
    private String title;
    public SampleMode(String title) {
        this.title = title;
    }
    public void setTitle(String title) {
        this.title = title;
        notifyPropertyChanged(BR.title);
    }
    @Bindable
    public String getTitlt() {
        return title;
    }
}

위에서 BR은 빌드시 생성이 됨.

위 SampleModel 객체 생성 후 setTitle 메소드 사용시 notifyPropertyChanged()를 통해 ***Binding.java에 변경이 전달 됨.

***Binding.java에서는 getTitle()을 통해 바뀐 값을 전달받음.

그래도 불편하므로 구글에서는 아래와 같은 것들을 제공함

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ...
  • ObservableArrayList
  • ObservableArrayMap
  • ObservableParcelabe
  • ObservableField
    • ObservableField<String>
    • ObservableField<CustomMode>

SampleModel을 바꿔보자.

public static class SampleModel extends BaseObservalbe {
    public final ObservableFiedl<String> title;
    public SampleMode(String title) {
        this.title = new ObservableField<>(title);
    }
}

Listener의 Binding

  • Object 전달

    <Button
            ...
            android:onClick="@{model.clickListener}" />

    위와 같이 사용시 BindingMethod 사용이 필요함

    @BindingMethod(type=View.class, attribute="android:onClick", method="setOnClickListener")
  • Method 직접 연결

    <Button
            ...
            android:onClick="@{model:clickListener}" />
    <Button
            ...
            android:onClick="@{(view)->model.clickListener(view)}" />

    위와 같이 사용시 아래와 같이 사용함.

    public class MainActivity extend AppCompatActivity {
        public void onClicButton(View button) {
            //선언부의 view button은 람다에서 제거 가능
        }
    }

CustomView + Custom Listener

public class CustomTextView extends AppCompatTextView {
    public interface OnCustomEventListener {
        void onEvent();
    }
}
@BindingAdapter("onCustomEvent")
public void setOnCustomEventListener(CustomTextView view, CustomTextView.OnCustomEventListener listener) {
    view.setOnCustomEventListener(listener);
}

//또는 

@BindingMethod({
    @BindingMethod(type=CustomTextView.class, attribute="onCustomEvent", method="setOnCustomEventListener")
})
<com.blabla.CustomTextView
                           ...
                           app:onCustomEvent="@{()->activity.onCustomEvent()}"

리스너 바인딩 사용시 주의점

대부분 리스너들은 return이 void 형식이나 onLongClick 일경우 return이 boolean형식이라 잘못 쓸 경우 이상한 오류 발생함. 아직 데이터 바인딩에 대한 오류 지원은 미흡함으로 주의해야 함.

Two-way Binding

  • Binding : UI <- Model
  • InverseBinding : UI -> Model
  • Two-way Binding : 둘다
public class MainActivity {
    public void onClickDone() {
        binding.getModel().text.set("이렇게 초기화가 됩니다.");
    }
}
public class TwowayBindingModel {
    public final ObservableField<String> text = new ObservalbeField<>("");
}
<layout>
    <data>
        <variable
                  name="activity"
                  type="com.test.blabla.MainActivity" />
        <variable
                  name="model"
                  type="com.test.blabla.TwowayBindingModel" />
    </data>
    <LinearLayout
                  ... >
        <Button android:onClick="@{()->activity.onClickDone()}" />
        <EditText
                  android:text="@={model.text}" />
        <TextView
                  android:text="@{model.text}" />
    </LinearLayout>
</layout>

양방향 바인딩 : @={model.title}

단방향 바인딩 : @{model.title}

EditText는 양방향 바인딩이라 텍스트 수정시 InverseBinding이 이루어지며, 이 후 변경된 값으로 Binding이 이루어짐.

  • Binding

    @BindingAdapter("android:text")
    public static void setText(TextView textView, String text) {
        tetView.setText(text)
    }
  • InverseBinding

    @InverseBindingAdapter(attribute={"android:text"})
    public static String getText(TextView textView) {
        return tetView.getText().toString();
    }

InverseBinding 선언부

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface InverseBindingAdapter {
    String attribute();
    String event() default "";
}

InverseBinding에서 event 사용시

@InverseBindingAdapter(attribute={"android:text", event="textEvent"})
public static String getText(TextView textView) {
    return tetView.getText().toString();
}

@BindingAdapter("textEvent")
public static void setTextEvent(TextView textView, final InverseBindingListener listener) {
    textView.addTextChangedListener(new TextWatcher() {
        @Overrride
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            listener.onChange();
        }
    });
}

위 구현의 동작 흐름

  1. EditText에 텍스트 변경
  2. @BindingAdapter("textEvent")에서 텍스트 변경으로 InverseBindingListener의 onChange() 호출
  3. @InverseBindingAdapter(attribute={"android:text", event="textEvent"}) 에서 변경된 텍스트 반환
  4. TwowayBindingModel의 ObservableField인 text에서 값이 변경됨을 알림.
  5. @BindingAdapter("android:text") 으로 EditText와 TextView에 변경된 값을 설정함.

하지만 위와 같이 처리시 5번에서 다시 1번이 발생하게 되어 계속 반복하게 됨.

구글에서 이에 대한 직접 로직을 추가하여 막아야 한다고 함. (TextViewBindingAdpater 참조)

TextViewBindingAdpater 에서는 이전과 변경되는 텍스트 값을 비교하여 예외처리를 추가함.

<include>와 <ViewStub>

네이버 프리즘라이브의 경우 Activity가 pause 되면 안되는 상태로 설계가 되었고, 하나의 Activity에 많은 View를 추가하는 형태임.

  • 수많은 View들
  • 개발자들 사이의 분할(분담)
  • 기능들의 확장성
  • 변경의 용이성

(architecture나 패턴에 대한 부분은 제외하고 DataBinding과 View 관점에서만 논하기로 함)

main.xml -> include1.xml

​ -> viewstub1.xml

​ -> viewstub2.xml

메인에서 하위로 데이터를 전달해야 하므로 데이터도 동일하게 뎁스를 만들어서 사용함.

MainBindingModel -> IncludeBindingModel

xml에서 하위로 모델 넘길 경우

<!-- main.xml -->
<layout>
    <data>
        <variable
                  name="model"
                  type="MainBindingModel" />
    </data>
    <include
             layout="..."
             app:mode2="@{model.includeModel}" />
</layout>
<!-- main.xml -->
<layout>
    <data>
        <variable
                  name="model2"
                  type="IncludeBindingModel" />
    </data>
    <TextView
             app:mode2="@{model2.title}" />
</layout>

프리즘은 각 View별로 ViewModel 1:1 매핑하여 개발함.

ViewStub 바인딩시 include보다 까다로움.

ViewStub도 View 상속하다보니 일반적인 BindingAdapter를 사용가능하나,

ViewStub 특성을 살리는 부분은 ViewStub를 파라미터로 받는 BindingAdpater로 구현해야 함.

하지만 이는 Gradle 3.1.0이상부터 되는 것으로 보여 그 이하의 환경에서는 편법을 써야 함.

ViewStub를 DataBinding시에 ***Binding.java에는 ViewStubProxy가 사용됨.

그 ViewStubProxy 안에 ViewStub이 있음.

ViewStub을 사용시에는 반드시 android:id 가 있어야 함. (없을 경우 에러로그가 딱히 안나옴)

최초에는 inflate 필요함. ViewStub의 경우 ViewStubProxy에서 inflat시에 다시 바인딩(rebinding)을 하게 함.

위의 이유로 애니메이션 동작 등이 오동작할 수 있음.

다시 바인딩되는 범위는 ViewStub을 싸고 있는 부모뷰까지이므로 편법으로 ViewStub을 include로 싸면 되긴 함.

(대신 아름답지는 않음)

Q&A

Q. 협업을 위해 바인딩 어답터 오버로딩한다고 하셨는데, 바인딩어답터에서 바인딩어답터를 호출할 수 있습니까?

넵! 이미 BindingAdapter로 넘어오면 그 시점부터는 Java Method라고 보셔도 무방합니다. 즉… 필요한 BindingAdapter의 Method를 호출해주면 됩니다..!

Q. 바인딩 문법 틀렸을때 자바처럼 잘 디버깅해주지않던데 쉽게 문법오류 잡는 팁 있을까요?

안타깝게도… 이건 좋은 방법이 있진 않아요. 좋은 방법이라면… 수없이 많이 맞아보는 것…?ㅎㅎ…

Q. 동적으로 ui위치를 변경하고자할때 데이터바인딩을 사용하고 싶은데 뷰의 속성변경 말고 뷰 객체 자체를 받을 수 있나요?

결론적으로… 저라면 동적으로 UI위치를 변경하고자 한다면 예를들면 이렇게 작성할 것 같아요..

@BindingAdapter](https://github.com/BindingAdapter)( {"positionX", "positionY"} ) public static void moveView(View view, float positionX, positionY) { view.setTranslationX(positionX); view.setTranslationY(positionY); }

Q. recyclerView의 스크롤 이벤트 같은것도 데이터 바인딩으로 받을 수 있을까요? (스크롤 위치에 따라서 이벤트 처리를 하고 싶은 경우에)

해보지는 않았는데요. Listener를 Setting해서 값을 가져오는 방식이라면 가능합니다. InverseBindingAdapter를 이용하면 될것 같은데요.

참고로 저의 경우에는 ViewPager의 Page 이동과 offset 이동도 InverseBinding으로 처리하고 있습니다.

Q. 데이터바인딩을 사용하게 되면서 코드 가독성이 떨어진 점은 없었나요? Textview.setText면 충분하지 않았나 싶은 생각도 들고 그러는데 데이터바인딩의 장점이 궁금합니다.

이건 취향차이기인 해요. 저는 오히려 코드 가독성이 올라가는 부분도 있다라고 생각합니다. View에 대한 로직은 모두 xml에서 볼 수 있다라고 생각할 수 있으니까요. 로직이 복잡해지면 xml도, 코드에서도 복잡하긴 한건 매한가지 이긴 하니까…?

사실. 이 DataBinding은 코드의 가독성을 높인다. 개발 생산성을 높인다.의 이야기와는 맞진 않다고 생각합니다. 정확하게는 Java/코틀린 코드에서 View에 대한 참조를 제거하는 도구이다. 라고 보시는 쪽이 맞긴 해요. 또한… 이런저런 장점들이 있기는 한데… 가장 크게 예를 들면 Image Loading 같은 경우에요. 그냥 코드로 작성하게 되는경우 Image를 Load하는 모든 지점에서 자바/코틀린으로 Loading 로직을 작성해주셔야 하는데 Binding을 이용한다면 이런식으로 작성하면 끝! 할수도 있거든요

막상 열심히 써보면 편해요. 굉장히 편합니다. View를 사용할 때 thread 걱정도 사라지고요. 얘가 NullPointer도 잡아주고요… 내 손으로 작성하는 코드의 양을 줄여서 실수를 줄여주는 부분도 있습니다. 이제 저는… Binding 없이는 코딩이 좀 어렵다. 생각이 들 정도로 적어도 제게는 정말 편리한 도구입니다



+ Recent posts