pinkred's mobile program

pinkred mobile programer

Weverse Company (2019년 1월 28일 ~ 현재)

leave a comment »

Written by pinkredmobile

2019/04/06 at 6:55 pm

경력사항에 게시됨

Tagged with

책의 시작 – 프레임

leave a comment »

올해 만큼 책을 많이 읽은 해는 없을것 같습니다.

한동안 책을 계속 읽다보니 내가 도대체 어떤 책을 읽었고 어떤 내용이였는지에 대한 필요성을 느끼게 되었습니다.

여태까지 읽은 책들을 찾으면서 정리해보도록 하면 나중에 뿌듯할수 있을지.

 

고고고

시작의 의미로 프레임이라는 책입니다.

이 책은 어느날 나에게 충격적이 사건이 같은날 2가지가 발생하면서 나를 다시 돌아보기 위해서 구입한 책으로 시작은 개인주의로 시작해서 어떤 사람이 되어야 할 것인가를 찾기위해서 밤늦게 스마트폰을 보다가 이책을 찾고 서점으로 당장 달려가서 책을 찾다가 서점 문닫는 시간이라고 해서 급히 책의 존재여부를 물어보고 한권 남은 책을 후딱 사서 열심히 읽어본 책으로 내가 얼마나 좁은 마음과 좁은 눈으로 살고 있었는지 다름 사람과 살기 위해서는 어떻게 해야할지에 대한 기반을 마련해 준 책이다.

20170803_064157.jpg

사람은 항상 자기만의 프레임을 만들고 그곳을 통해서 보려고 한다. 마치 집에 창문을 보면 창문의 틀이라는 기준이 생기면 창문을 통해서 바깥 세상을 본다라는 느낌이다. 프레임은 자기를 가두어 두는 하나의 틀이기 때문에 이 틀을 어떻게 개선해 가느냐에 따라서 나의 생각, 표현, 시선등의 모습을 바꾸어 나갈수 있다.  물론 다른 사람은 내가 만들어 놓은 프레임으로 나를 보게 될것이다. 그 프레임이 서로에게 다르게 느낄수 있을 것이다.

대화 중요성에 대해서도 강조하고 있다. 프레임을 만드는 것은 나지만 해당 프레임을 보여주기위해서 가장 중요한 것중에 하나가 대화이다. 프레임은 어떻게 보면 내가 바라보는 관점도 중요하지만 남이 나를 어떻게 보는지도 중요하다. 난 리무진안에서 창밖을 보고 있다고 생각할수 있지만 상대방은 티코에 탄 내모습을 볼수도 있다.

프레임은 항상 개선할수 있다. 어떻게 개선할지 어떤 모습으로 바꿀지는 내가 선택하는 것이 행동하는 것이다.

Written by pinkredmobile

2018/10/19 at 9:20 am

책(Book)에 게시됨

Tagged with

코드로 대화하기 – Null 만날지 말지~!

leave a comment »

Null 만날지 말지~!

null은 항상 java에서 골치꺼리이다. Class에서 null을 보기 싫다면 의도적으로 null이 되지 못하게 해라.

class Cart
{
    private List<String> cartList;

    public Cart()
    {
        cartList = new ArrayList();
    }

    public void addItem(String item)
    {
        if (item == null || item.isEmpty() == true)
        {
            throw new EmptyItemException("item == null || item.isEmpty() == true");
        }

        cartList.add(item);
    }

    public void addAllItem(List<String> cartList)
    {
        if (cartList == null || cartList.size() == 0)
        {
            throw new EmptyItemException("item == null || item.isEmpty() == true");
        }
        this.cartList.addAll(cartList);
    }

    public List<String> getCartList()
    {
        return cartList;
    }
}

코드에서 cartList는 null이 될수 없다. 그래서 getCartList()를 호출하더라도 안심하고 null 체크 없이 사용이 가능하다. null을 만날지 말지 고민하지 말고 만나기 싫으면 의도적으로 못 만나게 해라. 틈이 보이면 만날수 밖에 없다.

Written by pinkredmobile

2018/03/16 at 8:18 pm

프로그래밍(programming)에 게시됨

Tagged with ,

코드로 대화하기 – 숨기고 싶으면 보여주지 말아라~!

leave a comment »

숨기고 싶으면 보여주지 말아라~!


class Cart
{
    private List cartList;
    public Cart()
    {
        cartList = new ArrayList();
    }

    public void addItem(String item)
    {
        if (item == null || item.isEmpty() == true)
        {
            throw new EmptyItemException("item == null || item.isEmpty() == true");
        }

        cartList.add(item);
    }

    public void addAllItem(List&lt;String&gt; cartList)

    {
        if (cartList == null || cartList.size() == 0)
        {
            throw new EmptyItemException("item == null || item.isEmpty() == true");
        }
        this.cartList.addAll(cartList);
    }
}

cartList의 타입을 외부에 노출 시키고 싶지 말아야 할때는 확실히 숨겨야한다.  안보여서 나쁠것은 없다. 보이면 자꾸 건드리고 싶어진다.

Written by pinkredmobile

2018/03/16 at 8:09 pm

프로그래밍(programming)에 게시됨

Tagged with ,

코드로 대화하기 – 목적을 분명하게 밝혀라~!

leave a comment »

목적을 분명하게 밝혀라~!

코드에는 목적을 분명히 노출 해줄수 있도록 해야한다.

class Cart
{
    public List cartList;

    public Cart()
    {
        cartList = new ArrayList();
    }
}

 


class Cart
{
    private List&amp;lt;String&amp;gt; cartList;

    public Cart()
    {
        cartList = new ArrayList();
    }

    public void addItem(String item)
    {
        if(item == null || item.isEmpty() == true)
        {
            throw new EmptyItemException("item == null || item.isEmpty() == true");
        }

        cartList.add(item);
    }

    public void addAllItem(List&amp;lt;String&amp;gt; cartList)
    {
        if(cartList == null || cartList.size() == 0)
        {
            throw new EmptyItemException("item == null || item.isEmpty() == true");
        }

        this.cartList.addAll(cartList);
    }
}

위의 코드를 보면 cartList의 값을 public로 하여 모든 것을 다 허용해 주겠어라는 식의 대화법을 나타냈다.

난 너를 위해 모든것을 다 줄수 있다는 대화법으로 잘 이용하면 좋지만 잘못 이용하면 난장판이 될수 도 있고 어떻게 관계를 이어가는데도 쉽지 않을수 있다.

아래 코드는 나와 대화하기 위해서는 특정 통로 로만 접근이 가능해. 그리고 지켜야할 약속들이 있어라고 말하고 있다.

어떻게 보면 밀당 같지만 너와 나와의 연결고리를 확실히 하고 있다.

목적이 무엇인지를 생각해보자.

Written by pinkredmobile

2018/03/16 at 9:19 am

프로그래밍(programming)에 게시됨

Tagged with ,

ConstraintLayout 쉽게 개발하기

leave a comment »

ConstraintLayout을 들어보지 못한 안드로이드 개발자가 없을 것이다. 하지만 적용해보았는지 물어보면 대부분 어렵다거나 혹은 버그가 많다거나 해서 쉽게 적용하지 못한 것으로 판단된다. 물론 잘 적용하시고 계신 분들도 많으리라 생각됩니다.

그럼 왜 적용하기 어려울까? 대부분 한글로 쉽게 설명된 자료가 없을 것이다. 물론 많은 관련 자료는 있지만 대부분 레퍼런스 자료 이거나 영문으로 되어있어서 쉽게 이해를 하지 못하기도 한다.

ConstraintLayout은 RelativeLayout의 확장형이라고 생각된다. 물론 성능은 더 좋아지고 쉽게 개발할수 있도록 해준다. 가 구글에서 말하는 거지만 실제 적용하려고 하면 다들 힘들어한다. 몇가지 개념을 알고 접근해야 한다. 어려운 용어는 버리고 쉬운 용어로 접근해 보도록 하겠다.

다들 아시겠지만 layout 구성시 가장 중요한 것은 깊이를 만들지 않는 것이고 그것을 도와줄수 있도록 ConstraintLayout이 많은 부분을 지원한다.

  1. 기존에는 match, wrap, dp로 width, height를 정의 했을 것이다. 이부분이 많이 달라졌다. 기존에는 match로 하면 자동으로 부모의 넓이를 가지게 되었다.  그리고 margin은 부모의 넓이를 기준으로 동작하였다. ConstraintLayout에서는 해당 정의가 약간 바뀌었다.

위의 이해가 가장 어려운데 이제는 부모가 중심이 아니고 나를 중심으로 한다. 예를 들어서 match 로 width를 정의하려고 하면

 

width = 0dp

layout_constraintLeft_toLeftOf = “parent”

layout_constraintRight_toRightOf = “parent”

위와 같은 방식으로 width를 0으로 주고 좌, 우를 부모를 중심으로 한다. 물론 부모인 ConstraintLayout는 match_parent로 되어있어야 한다. 이러한 방식으로 left, right, top, bottom을 설정하게 된다. 또한 ConstraintLayout에 있는 child는 꼭 left, right 와 top, bottom이 설정되어있어야 한다. 무슨 말이야 하면 left, right 값중에 한가지 꼭 left_toLeftOf가 아니더라고 기준이 될수 있는 left, right 값이 꼭 있어야하고, top, bottom도 둘중에 기준이 되는 값은 꼭 있어야 한다. 이 기준값을 넣어주지 않으면 Android 4.x에서 제대로 보여지지 않거나 원하지 않는 모습이 나올수도 있다.  정말 중요하다.

예를 들어서 위의 예제에서 Top, Bottom의 기준이 없지만 아마 xml에서는 그냥 자동으로 Top으로 보이것이다. 하지만 속지 말아야 한다. 제대로 동작하지 않을수 있다. 그러므로 상단으로 부터 시작한다면 layout_constraintTop_toTopOf = “parent”를 꼭 넣어주어야 한다. parent가 아니어도 상관없으니 꼭 넣도록 하자.

그럼 width, height의 기준을 잡아주는 내용을 살펴 보도록 하자.

layout_constraintLeft_toLeftOf

왼쪽의 기준은 어떻게 잡을지 결졍한다. “parent”를 선택하면 부모와 동일한 왼쪽 기준이 되고 다른  view를 선택하면 해당 view의 왼쪽 선과 동일하게 된다.
layout_constraintLeft_toRightOf

왼쪽 뷰의 오른쪽에 위치한다. 왼쪽에 뷰가 있다면 그 뷰의 오른쪽에 위치한다.
layout_constraintRight_toLeftOf

오른쪽 뷰의 왼쪽에 위치한다. 오른쪽에 뷰가 있다면 그 뷰의 왼쪽에 위치한다.
layout_constraintRight_toRightOf

오른쪽의 기준은 어떻게 잡을지 결졍한다. “parent”를 선택하면 부모와 동일한 오른쪽 기준이 되고 다른  view를 선택하면 해당 view의 오른쪽 선과 동일하게 된다.
layout_constraintTop_toTopOf

상단의 기준은 어떻게 잡을지 결졍한다. “parent”를 선택하면 부모와 동일한 상단 기준이 되고 다른  view를 선택하면 해당 view의 상단 선과 동일하게 된다.
layout_constraintTop_toBottomOf

상단 뷰의 하단에 위치한다. 상단에 뷰가 있다면 그 뷰의 하단에 위치한다.
layout_constraintBottom_toTopOf

하단 뷰의 상단에 위치한다. 하단에 뷰가 있다면 그 뷰의 상단에 위치한다.
layout_constraintBottom_toBottomOf

하단의 기준은 어떻게 잡을지 결졍한다. “parent”를 선택하면 부모와 동일한 하단 기준이 되고 다른  view를 선택하면 해당 view의 하단 선과 동일하게 된다.
layout_constraintBaseline_toBaselineOf
baseline을 가지고 있는 뷰와  baseline을 일치시킨다.(ex. TextView)

 

위의 기준을 잡아주면 기본적이 배치는 끝났다. 디자인 툴을 사용하여 연결해도 좋은데 직접 코딩해서 해당 기준선들을 넣어주는 것들이 나중에는 더 편하다. 왜냐하면 디자인 툴을 이용하면 드래그로 연결하는 것도 힘들고 실제 값이 들어있지 않는 경우에는 폭이나 높이가 좁아서 연결하기도 힘들다. 그래서 옆에 preview을 띄워 높고 직접 코딩으로 해당하는 값들을 넣어주는 것이 오히려 더 편했다.

ConstraintLayout을 변경시키려고 무작정 부모 Layout을 변경하면 자동 툴 변화로 자식 값의 위치값이 강제로 할다되는 경우가 발생하기 때문에 원본을 따로 복사해두고 하나씩 추가하는 방식으로 변경하는 것이 좋고 match값이 있는 경우에는 0dp로 우선 바꾸어 주고 copy and paste 해주는 것이 값이 변경되지 않는다.

ConstraintLayout의 기준을 설정되면 해당 기준으로 margin값을 정의 할 수 있다. 무슨 말이냐하면 기준값을 left만 넣게 되면 marin_right값을 정의 하더라도 반영이 되지 않는다. 무조건 기준이 되는 값을 넣고 그에 해당하는 marin값을 넣어야 반영된다.  기준값에 left, top이 정의되어있으면 margin은 left, top 만 가능하다는 말이다.

다음 시간에는 view 간의 연결 방식에 대해서 설명하겠습니다.

Written by pinkredmobile

2017/10/02 at 5:09 pm

Firebase A/B Test 하기

leave a comment »

A/B테스트를 할수 있는 플랫폼은 많이 있다.  회사마다 다양항 방식으로 진행하겠지만 Firebase를 이용해서 진행하는 방식을 해보기로 하겠다.

Firebase A/B Test는 Remote Config를 통해서 진행할수 있다.  구글개발자문서

Firebase가 제공하는 것은 A,B 인지를 제공하는 정도이다.  실망이 많을수 있다. 그럼 분석은 어떻게? 분석은 내부에서 사용하고 있는 것으로 사용해야 한다.

여기서 소개하는 것은 A/B를 제공하는 방식을 어떻게 Firebase 를 통해서 받을수 있을까에 대해서 소개하도록 한다.

스크린샷 2017-09-30 오후 5.49.15

A/B 테스트는 Remote Config의 CONDITIONS을 통해서 진행된다. 대략적인 진행 방식은 아래와 같다.

  1.  내가 테스트 하고 싶은 조건을 선택한다.
  2. 해당 조건으로 A, B인지의 조건을 부여한다.
  3. 앱에서 Remote Config를 통해서 A,B인지에 따라서 관련된 코딩을 진행한다.
  4. 해당 코딩에 분석툴을 추가하여 A, B의 결과를 분석한다.

Remote Config는 1,2번을 할수 있도록 지원한다.

조건은

스크린샷 2017-09-30 오후 5.57.18

  1. 앱은 Remote Config 에는 여러가지 앱을 등록할수 있다. 해당 앱중에 한가지를 선택한다.
  2. 앱을 선택하면 버전을 넣을수 있는데 해당 앱의 버전을 여러가지 조건으로 거를수 있다.
  3. 운영체제도 선택 가능한다. iOS, Android,

 

아래 조건은 주로 사용하는 부분은 임의 백분위수의 사용자입니다.

스크린샷 2017-09-30 오후 5.55.33

 

Remote Config에서 조건을 선택하는 방식은 크게 2가지 방식이 있다.

  1. 모든 조건을 한가지에 거는 방식
  2. 조건을 하나씩 선택해서 거르는 방식.

내 무슨 말인지 모르겠다구요. 설명 드리도록 하겠습니다. 해당 내용은 저도 너무 궁금해서 구글 개발자지원을 받아서 알아낸 것입니다.

예시로 테스트 조건을

OS : Android

버전 : 1.0

모수 : A : 10%, B : 10, 나머지는 기본으로

  1. 모든 조건을 한가지에 거는 방식은 아래와 같이 모든 조건을 걸어서 테스트 하는 방식이다.

스크린샷 2017-09-30 오후 6.06.40

스크린샷 2017-09-30 오후 6.07.19

참고로 B의 조건이 50~60으로 되어있는데 이렇게 한 이유는 추후에  A,B조건의 모수를 변경시에 유효범위를 위해서 입니다. 이렇게 하면 A는 0 ~50%가 유효범위고  B는 50~100% 가 유효범위이다.

2. 조건을 하나씩 거르는 방법은 아래와 같이 위에서 부터 조건을 거르는 것이다.

ios 유저를 처음에 거르면 안드로이드 유저만 남는다.

다음에 버전이 1.0이 아닌 사람을 거르면 1.0만 남는다.

그후에 A : 0~10,  B : 50~60 으로 나눈다.

스크린샷 2017-09-30 오후 6.20.19

위의 2가지 중에 어는 것이 효율적 일까 구글에 물어보니 아래의 방식이 조건이 더 효율적이라고 한다. 간단히 정리하자면 위의 방식의 조건의 개수가 아래 방식보다 많기 때문이다. 어차피 조건은 구글에서 만들어 처리해주는 것이니 아무거나 사용이 가능한데요. 아래와 같은 방식으로 하면 추후에 확장시(조건 변경)에 조금더 편리합니다

한번 조건에 걸린 유저는 앱을 삭제할때 까지 유지 된다. 재설치후에 변경될수 있다.

Remote Config를 통해서 A, B인지를 받아서 나머지 부분은 열심히 코딩하고 분석 툴을 넣어주면 된다. 물론 어떤 플랫폼은 분석까지 제공해주지만 기본적으로는 A,B를 제공해주는 것으로 만족해하고 있다. 여태까지 테스트를 하면서 가장 큰 이슈는 정말 A,B 를 잘 나누어 주는지였는데 난 구글을 믿고 싶었지만 대부분의 사람들이 신뢰를 하지 않았다. 몇번의 테스트를 통해서 모수를 잘나누어 준다고 결과가 리포트 되었고 이후로는 관련 이슈는 없었다.

Written by pinkredmobile

2017/09/30 at 6:31 pm

Android MVP pattern, Clean Architecture more

leave a comment »

개발을 하다보면 정형화된 틀을 생각하게 된다. 이는 혼자서 개발하든 협업을 하던지 이러한 틀이라는 것을 고민하게 된다. 한동안 안드로이드에서는 MVP 패턴에 대해서 많은 이야기들이 있었고, 구글에서는 기본적인 제안(Android Architecture Components)를 하기 시작했다.  이 글은 아직 구글이 제시하는 안 이전에 구현된 것으로 추후에는 안드로이드가 제공하는 컴포넌트를 연동하게 된다면 다시 한번 그후에 글을 써보도록 하겠다.

현제 추세가 MVP  패턴에 대해서 이야기 하고 있고 MVVM 패턴으로 넘어가기 시작 단계에 있다고 생각된다. 물론 MVP 패턴을 적용하더라도 큰 이슈는 없으며 전체적인 바향은 의존성(Dependence)을 제거하기 위한 방법으로 진행되고 있다고 생각된다.

Clean Architecture를 도입하고 시작한 것은 결국 패턴의 이슈로는 의존성을 완전히 없앨수 없기 때문에 추가적으로 아키텍쳐가 도입되어야 했다.  기존의 패턴은 수평관계의 의존성을 제거 한다면 아키텍처 도입으로 수직 관계의 의존성을 제거하였다. 물론 이러한 내용들을 받아들이는 사람들의 생각에 따라서 다르게 해석하고 다른 소스로 구현되어진다. 결국 큰 방향은 소드 코드의 의존성을 제거하는 쪽으로 흐른다고 볼수 있다.

위의 구조를 만들기 위해서 많은 라이브러리를 도입하게 되면 실제 학습의 시간이 가장 많이 소모 되므로 현 시점에서 가장 많이 쓰이는 라이브러리를 이용하여 구현 하였으며 실제 사용하고 있다.

Retrofit, OkHttp, RxAndroid, Retrolambda의 라이브러리를 이용하고 있다. 여기서 가장 어려운 학습은 역시 RxAndroid이다. 요즘에는 RxJava가 대세이다보니 많은 라이브러리들이 지원을 하고 있어서 적용시 큰 이슈는 없다. Dagger2를 사용하면 좋지만 아직 사용해본적이 없어서 관련 라이브러리는 배제하도록 하겠습니다.

이전글에 MVP 패턴에 대해서 간략적으로 설명하였으나 역시나 사용하다보니 불편한 점들은 업데이트가 되고 아키텍쳐와 합쳐지면서 더욱더 복잡해졌다.

Clean Architecture 적용의 목적은 순수 자바로 만들어서 의존성을 제거한다라고 생각하면 된다.

패키지 구조를 보도록 하겠습니다. 참조 사이트

domain :  모델을 제공 받는 방식에 대한 인터페이스입니다.

entity : 순수 모델입니다.

repository.local : 내부에서 모델을 제공. domain에 해당하는 인터페이스를 구현

repository.local.model : 내부모델

repository.remote : 외부에서 모델을 제공. domain에 해당하는 인터페이스를 구현

repository.remote.model : 외부모델

domain 샘플


public interface CommonInterface
{
Observable<CommonDateTime> getCommonDateTime();
}

repository.remote 샘플


public class CommonRemoteImpl implements CommonInterface
{
private Context mContext;

public CommonRemoteImpl(@NonNull Context context)
{
mContext = context;
}

@Override
Observable<CommonDateTime> getCommonDateTime()
{
return DailyMobileAPI.getInstance(mContext).getCommonDateTime().map((commonDateTimeDataBaseDto) -&gt;
{
CommonDateTime commonDateTime = null;

if (commonDateTimeDataBaseDto != null)
{
if (commonDateTimeDataBaseDto.msgCode == 100 &amp;&amp; commonDateTimeDataBaseDto.data != null)
{
commonDateTime = commonDateTimeDataBaseDto.data.getCommonDateTime();
} else
{
throw new BaseException(commonDateTimeDataBaseDto.msgCode, commonDateTimeDataBaseDto.msg);
}
} else
{
throw new BaseException(-1, null);
}

return commonDateTime;
}).observeOn(AndroidSchedulers.mainThread());
}
}

repository.remote.model 샘플


@JsonObject
public class CommonDateTimeData
{
@JsonField(name = "openDateTime")
public String openDateTime;

@JsonField(name = "closeDateTime")
public String closeDateTime;

@JsonField(name = "currentDateTime")
public String currentDateTime;

public CommonDateTimeData()
{
}

public CommonDateTime getCommonDateTime()
{
return new CommonDateTime(openDateTime, closeDateTime, currentDateTime);
}
}

entity 샘플


public class CommonDateTime
{
public String openDateTime;
public String closeDateTime;
public String currentDateTime;

public CommonDateTime()
{
}

public CommonDateTime(String openDateTime, String closeDateTime, String currentDateTime)
{
setDateTime(openDateTime, closeDateTime, currentDateTime);
}

public void setDateTime(String openDateTime, String closeDateTime, String currentDateTime)
{
this.openDateTime = openDateTime;
this.closeDateTime = closeDateTime;
this.currentDateTime = currentDateTime;
}
}

 

사용 방법


mCommonRemoteImpl.getCommonDateTime()//
.subscribe(commonDateTime ->;
{
onCommonDateTime(commonDateTime);
}, throwable -&gt;
{

}));

 

아래와 같은 구조로 되어있다.

스크린샷 2017-07-26 오후 4.09.36

domain에는 interface을 정의하여 데이터를 얻는 방식의 의존성을 버린다.

그리고 따로 로컬/네트워크 모델을 만들어서 저장소의 의존성을 버린다. 여기서 방식이란 예를 들어서 네트워크 / 로컬 데이터를 얻는 라이브러리를 어떠한 것 이용하는 경우이다. 결국 최종으로 얻는 CommonDateTime의 순수 모델은 각각의 단계에서 어떻게 얻어지는 몰라도 상관없다. 관련된 내용은 Clean Architecture의 의존성을 버리는 방식이다.

 

그렇다면 여기서 패턴을 추가 할수가 있다. 기본적을 MVP 패턴을 사용하도록 하겠다.

 

스크린샷 2017-07-26 오후 4.34.19

결국에는 Activity 조차도 의존성을 버리기 위해서 Interface를 두었다. 물론 Presenter, View도 Interface를 두어서 의존성을 없앴다. 계속적인 Interface로 의존성은 떨어지고 있지만 파일의 개수가 늘어나게 된다. 그래서 패키지를 나눌때 같은 속성 별로 패키지를 하지 않고 스크린 단위로 패키지를 하여 같은 스크린안에서 사용하는 파일들을 모아서 쉽게 접근할수 있도록 했다.

그리고 BasePresenter에서 Google Analytics 를 호출 할수 있도록 BaseAnalyticsInterface를 정의한다.

각각의 파일의 구조를 보도록 하겠다. 실제로는 더욱 복잡하지만 소스를 간추렸다.

BaseActivity는 BasePresenter와 연결되어서 Activity에 대한 의존성은 버린다. 대신 Activity는 Intent를 받는 역할로만 처리하였다.

BasePresenter는 BaseActivity, BaseInterface를 받아서 View와 의존성을 버리고, Analytics를 넣으므로써 OnBaseEventListener에서 발생하는 이벤트를 Google Analytics에 반영할수 있도록 했다.

BaseView는 OnBaseEventListener와 ViewDataBind를 받아서 Presenter와 의존성을 버리고 UI를 쉽게 접근할수 있도록 ViewDataBind로 처리하도록 했다. BaseView에서 activity변수를 받아서 약간 의아할수도 있다. 기존에는 Context를 보냈는데 사용하다 보니 View에서 어쩔수 없이 activity를 필요로 할때가 있는데 그래서 일반적으로 사용할때는 getConext()를 사용하여  Context를 넘겨주고 특별한 경우에만 activity를 이용하여 얻어갈수 있도록 따로 구현하였다. 물론 activity값을 View에서 직접 호출할수는 없다.

아직 부족한 점도 많고 새로이 뜨고 있는 MVVM 패턴과 구글에서 제공하는 AAC 라이브러리도 다시 적용해보아야 하기 때문에 계속적인 업데이트가 필요하지만 현시점에서 협업으로 사용하기에는 적절한 크기일것이라고 생각된다. 물론 접근하시는 분들의 입장에서는 다소 어렵다는 말씀이 있기도 하다. 추가적으로 Dagger2를 적용하는 부분도 고려해볼만하다고 생각됩니다.

 

이에 별도로 View의 xml이 점차 복잡해지기 때문에 해당 부분을 커스텀 뷰로 만들어서 재사용 목적으로 소스가 더 간략해질수 있다고 생각된다.

 

 

 

BaseActivity


public abstract class BaseActivity<T1 extends BasePresenter> extends AppCompatActivity
{
private BasePresenter mPresenter;

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

mPresenter = createInstancePresenter();

if (mPresenter.onIntent(getIntent()) == false)
{
finish();
return;
}

mPresenter.onPostCreate();
}

protected abstract
@NonNull
T1 createInstancePresenter();

public
@NonNull
T1 getPresenter()
{
if (mPresenter == null)
{
mPresenter = createInstancePresenter();
}

return (T1) mPresenter;
}

@Override
protected void onStart()
{
super.onStart();

if (mPresenter != null)
{
mPresenter.onStart();
}
}

@Override
protected void onResume()
{
super.onResume();

if (mPresenter != null)
{
mPresenter.onResume();
}
}

@Override
protected void onSaveInstanceState(Bundle outState)
{
if (mPresenter != null)
{
mPresenter.onSaveInstanceState(outState);
}

super.onSaveInstanceState(outState);
}

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

if (mPresenter != null)
{
mPresenter.onRestoreInstanceState(savedInstanceState);
}
}

@Override
protected void onPause()
{
super.onPause();

if (mPresenter != null)
{
mPresenter.onPause();
}
}

@Override
protected void onDestroy()
{
super.onDestroy();

if (mPresenter != null)
{
mPresenter.onDestroy();
}
}

@Override
public void onBackPressed()
{
if (mPresenter != null)
{
if (mPresenter.onBackPressed() == false)
{
super.onBackPressed();
}
} else
{
super.onBackPressed();
}
}

@Override
public void finish()
{
super.finish();

if (mPresenter != null)
{
mPresenter.onFinish();
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);

if (mPresenter != null)
{
mPresenter.onActivityResult(requestCode, resultCode, data);
}
}
}

BaseActivityInterface


public interface BaseActivityInterface
{
boolean onIntent(Intent intent);

void onStart();

void onResume();

void onPause();

void onDestroy();

void onFinish();

boolean onBackPressed();

void onSaveInstanceState(Bundle outState);

void onRestoreInstanceState(Bundle savedInstanceState);

void onActivityResult(int requestCode, int resultCode, Intent data);
}

BasePresenter


public abstract class BasePresenter<T1 extends BaseActivity, T2 extends BaseViewInterface> implements BaseActivityInterface
{
private T1 mActivity;

private T2 mOnViewInterface;

public BasePresenter(@NonNull T1 activity)
{
mActivity = activity;

mLock = new DailyLock(activity);

mOnViewInterface = createInstanceViewInterface();

constructorInitialize(activity);
}

protected abstract
@NonNull
T2 createInstanceViewInterface();

public abstract void constructorInitialize(T1 activity);

public abstract void setAnalytics(BaseAnalyticsInterface analytics);

public abstract void onPostCreate();

public T1 getActivity()
{
return mActivity;
}

public void setContentView(@LayoutRes int layoutResID)public interface BaseActivityInterface
{
boolean onIntent(Intent intent);

void onStart();

void onResume();

void onPause();

void onDestroy();

void onFinish();

boolean onBackPressed();

void onSaveInstanceState(Bundle outState);

void onRestoreInstanceState(Bundle savedInstanceState);

void onActivityResult(int requestCode, int resultCode, Intent data);
}
{
if (mOnViewInterface == null)
{
throw new NullPointerException("mOnViewInterface is null");
} else
{
mOnViewInterface.setContentView(layoutResID);
}
}

public
@NonNull
T2 getViewInterface()
{
if (mOnViewInterface == null)
{
mOnViewInterface = createInstanceViewInterface();
}

return mOnViewInterface;
}

@Override
public void onStart()
{

}

@Override
public void onResume()
{

}

@Override
public void onPause()
{
}

@Override
public void onDestroy()
{
}

@Override
public void onFinish()
{

}

@Override
public boolean onBackPressed()
{
return false;
}

@Override
public void onSaveInstanceState(Bundle outState)
{

}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState)
{

}

protected void finish()
{
mActivity.finish();
}
}

BaseViewInterface


public interface BaseViewInterface
{
void setContentView(int layoutResID);

void setContentView(int layoutResID, ViewGroup viewGroup);
}

BaseView


public abstract class BaseView<T1 extends OnBaseEventListener, T2 extends ViewDataBinding> implements BaseViewInterface
{
private BaseActivity mActivity;
private T2 mViewDataBinding;
private T1 mOnEventListener;

protected abstract void setContentView(T2 viewDataBinding);

public BaseView(BaseActivity activity, T1 listener)
{
if (activity == null || listener == null)
{
throw new NullPointerException();
}

mActivity = activity;
mOnEventListener = listener;
}

@Override
public final void setContentView(int layoutResID)
{
if (layoutResID != 0)
{
mViewDataBinding = DataBindingUtil.setContentView(mActivity, layoutResID);
}

setContentView(mViewDataBinding);
}

@Override
public final void setContentView(int layoutResID, ViewGroup viewGroup)
{
mViewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(mActivity), layoutResID, viewGroup, false);

setContentView(mViewDataBinding);
}

protected void setVisibility(int visibility)
{
if (mViewDataBinding == null)
{
return;
}

mViewDataBinding.getRoot().setVisibility(visibility);
}

protected
@NonNull
Context getContext()
{
return mActivity;
}

protected T2 getViewDataBinding()
{
return mViewDataBinding;
}

protected
@NonNull
T1 getEventListener()
{
return mOnEventListener;
}

protected int getColor(int resId)
{
return mActivity.getResources().getColor(resId);
}

protected String getString(int resId)
{
return mActivity.getString(resId);
}
}

OnBaseEventListener


public interface OnBaseEventListener
{
void onBackClick();
}

BaseAnalyticsListener

public interface BaseAnalyticsInterface
{

}

 

 

Written by pinkredmobile

2017/07/26 at 4:59 pm

Android Custom View 만들때 merge태그

leave a comment »

Custom View를 만들때 일반적으로 ViewGroup을 상속 받아서 layer를 inflate시켜서 구현하는데 layer의 깊이를 줄이기 위해서 merge태그를 사용하다보니 merge 태그안의 UI가 제대로 정렬되지 않아서 수정시에 다시 merge태그를 없애고 다시 원래 ViewGroup 태그를 넣어서 GUI  수정후에 주석 처리하는 방식으로 진행했는데 매번 보는 사람이나 수정하는 사람이나 매우 귀찮았다고 할수 있다. 해결 방안이다.

Written by pinkredmobile

2017/07/26 at 10:47 am

프로그래밍(programming)에 게시됨

Tagged with ,

Android Rxjava2

leave a comment »

  • Rxjava2를 시작한지 얼마 되지는 않았다.
  • 관련된 서적도 사보고 해보았지만 밀려드는 신 기술에 이론만 파악될뿐 실무에 적용하려고 하니 너무나 어려운 문제가 많았다. 그중에서 가장 어려운 이슈는 답이라는 것이다.  1에서 10까지 더하는  코드를 작성하고자 한다면 한 최소 3가지 이상의 방법이 있을것이다. 대략 어떤 방식으로 하면 좋을지는 어렵지 않게 생각한다. 하지만 Rxjava에서 이런 경우 어떻게 해야할까 라고 생각할테 마땅히 답을 줄수 있는 사이트도 찾아보기 힘들고 국내 관련 블로그도 쉽지 않다.
  • 그래서 내가 쓰면서 아 이렇게 하면 되더라를 체험하면서 얻는 내용들을 정리하려고 한다.
  • Rxjava2를 사용하고 RxAndroid를 연동하여 사용한다.
  • CompositeDisposable를 사용하여 관리한다.
  • ramda를 사용한다.
  • 네트워크는 retrofit2를 사용한다.

계속적으로 사용하면서 추가하도록 한다.

  1. 스케줄러는 스레드로 결과는 메인 스레드로 해야 할 경우(예시로 네트워크 연동)

    mMobileService.getSuggests(url, keyword)//
                .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
  2. class A →  class B의 형식으로 변환 시키고 싶은 경우.

    BaseDto -> List 로 변환하여 리턴
    Observable getSuggests(String keyword);
    MobileAPI.getInstance(context).getSuggests(keyword).map((suggestsDataBaseDto) ->
    {
        List list = null;
        if (suggestsDataBaseDto != null)
        {
            if (suggestsDataBaseDto.code == 100 && suggestsDataBaseDto.data != null)
            {
                list = suggestsDataBaseDto.data.getSuggestList();
            } else
            {
                throw new BaseException(suggestsDataBaseDto.code, suggestsDataBaseDto.messasge);
            }
        } else
        {
            throw new BaseException(-1, null);
        }
        return list;
    }).observeOn(AndroidSchedulers.mainThread());
  3. 특정 시간 후에 스케줄러가 동작하는 경우

    1) 스케줄러를 호출하고 특정 시간후에 결과를 받는다.

    addCompositeDisposable(suggestRemote.getSuggests(keyword)//
        .delay(5000, TimeUnit.MILLISECONDS).subscribe(suggests -> onSuggests(suggests), throwable -> onSuggests(null)));

    2) 특정시간 후에 스케줄러를 호출하고 결과를 받는다.

    addCompositeDisposable(suggestRemote.getSuggests(keyword)//
        .delaySubscription(500, TimeUnit.MILLISECONDS).subscribe(suggests -> onSuggests(suggests), throwable -> onSuggests(null)));
  4. 두개의 스케줄러가 끝날때까지 대기하여 둘다 끝나면 실행하는 경우.
Observable.zip(transitionObservable, networkObservable, new BiFunction<Boolean, Boolean, Boolean>()
{
@Override
public Boolean apply(Boolean o, Boolean o2) throws Exception
{
return null;
}
}).subscribe(new Consumer<Boolean>()
{
@Override
public void accept(Boolean aBoolean) throws Exception
{
ExLog.d("pinkred : " + aBoolean);
}
});

5. 순차적으로 실행시키고 싶을때 예를 들어서 시간을 얻은 후에 얻은 시간 값으로 상세화면을 얻고 해당 값으로 기타등등을 얻고 싶을때..

getCommonDateTime().flatMap(new Function<CommonDateTime, Observable<User>>()
{
@Override
public Observable<User> apply(@io.reactivex.annotations.NonNull CommonDateTime commonDateTime) throws Exception
{
return new ProfileRemoteImpl(getActivity()).getProfile();
}
}).flatMap(new Function<User, Observable<UserInformation>>()
{
@Override
public Observable<UserInformation> apply(@io.reactivex.annotations.NonNull User user) throws Exception
{
return new ProfileRemoteImpl(getActivity()).getUserInformation();
}
}).subscribe(new Consumer<UserInformation>()
{
@Override
public void accept(@io.reactivex.annotations.NonNull UserInformation userInformation) throws Exception
{

}
}));

6. RxJava로 뒤로가기 버튼 확인 기능 구현하기

Written by pinkredmobile

2017/05/04 at 9:11 am

프로그래밍(programming)에 게시됨

Tagged with ,