sonarqube – 코드 최적화하기

2분안에 설정하는 소나큐브

현재 진행중인 안드로이드 프로젝트를 개선하기 위해 소나큐브(http://www.sonarqube.org/)를 사용해보록 하겠습니다.

소나큐브는 프로젝트의 품질을 관리할 수 있도록 여러가지 모니터링 툴을 제공하는 오픈소스 플랫폼입니다. Java를 포함한 20가지가 넘는 프로그래밍 언어 (예: C#, C/C++, Javascript 등)로 제작된 프로젝트의 모니터링을 제공합니다.

참조

현재 환경은
OS : macOS sierra version 10.12.1
JAVA : 1.8


서버 다운로드 링크

https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-6.1.zip

스캐너 다운로드 링크

https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-2.8.zip

예제 파일 링크

https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-2.8.zip

위 파일을 다운로드 후 압축을 풀어준다.

소나큐브 서버 설치하기

$ mkdir ~/sonar
$ cd sonar
$ wget https://sonarsource.bintray.com/Distribution/sonarqube/sonarqube-6.1.zip
$ unzip sonarqube-6.1.zip
$ rm sonarqube-6.1.zip

소나큐브 스캐너 설치하기

$ cd ~/sonar
$ wget https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-2.8.zip
$ unzip sonar-scanner-2.8.zip
$ rm sonar-scanner-2.8.zip

소나큐브 샘플 파일 설치하기

$ cd ~/sonar
$ wget https://github.com/SonarSource/sonar-examples/archive/master.zip
$ unzip master.zip
$ rm master.zip

환경변수 설정하기

$ emacs ~/.bashrc

아래 항목들을 파일에 추가해준다.

#sonar-qube
export PATH=/Users/username/sonar/sonarqube-6.1/bin/macosx-universal-64:$PATH
export PATH=/Users/username/sonar/sonar-scanner-2.8/bin:$PATH

소나큐브 서버 시작하기

$ sonar.sh console

소나큐브 프로젝트 스캔하기

프로젝트 위치에서 아래의 명령을 실행해주자.
!아래의 명령은 코드 분석을 시작하는 것이므로 프로젝트에는 이전에 프로젝트 설정파일이 포함되어 있어야 한다.

$ sonar-scanner

소나큐브 프로젝트 파일 생성하기

$ cat project/sonar-project.properties

위 파일안에 아래의 내용들을 추가해주자.

sonar.projectKey=my:project
# this is the name displayed in the SonarQube UI
sonar.projectName=Linkpay
sonar.projectVersion=1.0

# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
# Since SonarQube 4.2, this property is optional if sonar.modules is set.
# If not set, SonarQube starts looking for source code from the directory containing
# the sonar-project.properties file.
sonar.sources=.
sonar.exclusions=**/*$$ViewBinder.java,**/*R.java

# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8

sonar.projectKey : 소나큐브 사이트 URL에 포함될 키
sonar.projectName : 소나큐브 사이트에서 보여질 프로젝트 이름
sonar.projectVersion : 소나큐브 사이트에서 보여질 프로젝트 버전
sonar.sources : 프로젝트 경로
sonar.exclusions : 분석 제외할 파일 패턴

끝.

Android – getFragment NullPointerException 문제 해결하기

MaterialNavigationDrawer와 ViewPager (v4 22.2.0)를 사용 할때 (즉 다중 Fragment를 사용했을 때) getFragment 시 NullPointException 문제가 발생합니다.

정확한 이유는 아직 찾지 못했으나, stackoverflow에서 동일한 문제를 겪고 있는 분들이 있더라고요. 일단 증상부터 해결책을 살펴보도록 하겠습니다.

일단 에러 코드는 아래와 같습니다.

java.lang.NullPointerException
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:569)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:211)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1281)
at android.view.View.dispatchRestoreInstanceState(View.java:12043)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2688)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2694)
...

일단 문제의 원인은 childFragment 이슈 때문에 발생하는데, 아래의 방법은 그렇게 확실하지 않은 방법이라고 하네요. 원인은 states 복구 쪽에서 일어나는 거 같습니다.

그래서 아래의 해결책은 상태값을 저장하지 못하게 되는데요, 이부분을 사용하지 않을거라면 아래의 방법으로 해결하여도 무방할거 같습니다.

아래의 부분을 FragmentStatePagerAdapter 상속한 클래스에 넣어주시면 됩니다.
아래의 코드는 상태 복구부분에서 아무것도 안하는 것을 확인 할 수 있습니다.

    @Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
    //do nothing here! no call to super.restoreState(arg0, arg1);
}

끄읕.

Android – Robolectric를 활용한 Unit Testing

원문

Robolectirc 설정

RobolectricJUnit 4(단말기 필요)는 JVM환경에서 안드로이드 어플리케이션을 테스트 할 수 있는 프레임웍이다. 좀 더 많은 정보는 여기에서 얻을 수 있다.

테스트 환경

  • Android Studio 1.3.1 (1.2.1.1++)
  • Android Gradle Plugin 1.3.0 (1.2.3+)
  • Gradle 2.3.0 (2.2.1+)

Note. Robolectirc은 Android Studio 1.1에서 구동 될 수 있으나, Robolectric Gradle Plugin과 별도의 설정이 필요하다.

또한 Android Studio 1.1에서의 unit testing은 여기에서 확인 할 수 있다.

App build.gradle

첫번째, app 폴더 안에 build.gradle안에 아래의 내용을 추가해야 한다. 현재 최종 버전은 3.0.

dependencies {
    ...
    testCompile 'org.robolectric:robolectric:3.0'
  }

Android Studio 설정

다은 스탭으로.. Unit Testing을 하기 위해 몇가지 설정 할게 필요한데, 첫 번째로 왼쪽 사이드 메뉴에 있는 Build Variants눌러 준다. Test Artifact를 Unit Tests로 선택해준다.

각 타입 별 테스트를 위해, Android Studio는 아래와 같이 경로들을 사용한다. * Unit Tests => src/test/java * Android Instrumentation Tests => src/androidTest/java

기본으로 되어있는 Test는 Android Instrumentation Tests로 Unit Tests경로로 바꿔 준다. 가장 쉬운 방법으로 rename 이름 변경 기능을 사용하자.

Note. 맥을 사용한다면, 경로 이슈가 있는데 아래와 같이 변경해주면 문제를 해결 할 수 있다.

Go to Run -> Edit Configurations -> Default -> Junit으로 이동하면 working directory를 [code]$MODULE_DIR$[/code]로 변경해준다.

좀 더 많은 자료는 여기에서 확인 할 수 있다.

모든 설정이 완료 되었다.

Robolectric test 코드 작성

  • Project Name : RSample
  • MyApplication
public class MyApplication extends Application {
    public LoginHandler getLoginHandler() {
        return new LoginHandler();
    }
}
  • LoginHandler
public class LoginHandler {
    public void login(String id, String pwd) {
    }
}
  • MainActivityTest
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;

import static java.lang.System.out;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLog.stream;

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
    MainActivity activity;
    private LoginHandler loginHandler;
    private MyApplication mockedApplication;

    @Before
    public void setup() throws Exception {
        stream = out;

        activity = Robolectric.setupActivity(MainActivity.class);
        activity = Mockito.spy(activity);

        mockedApplication = mock(MyApplication.class);
        loginHandler = mock(LoginHandler.class);
        when(mockedApplication.getLoginHandler()).thenReturn(loginHandler);
        doReturn(mockedApplication).when(activity).getApplication();
    }

    @Test
    public void validateTextViewContent() throws Exception {
        TextView tvHelloWorld = (TextView) activity.findViewById(R.id.a_main_titleTv);
        assertThat(tvHelloWorld.getText().toString(), is("Hello world!"));
    }

    @Test
    public void login() throws Exception {
        Button loginBtn = (Button) activity.findViewById(R.id.a_main_loginBtn);
        loginBtn.performClick();
    }
}
  • MainActivity
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final EditText idEt = (EditText) findViewById(R.id.a_main_idEt);
        final EditText pwdEt = (EditText) findViewById(R.id.a_main_pwdEt);
        Button loginBtn = (Button) findViewById(R.id.a_main_loginBtn);

        loginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String id = idEt.getText().toString();
                String pwd = pwdEt.getText().toString();
                login(id, pwd);
            }
        });
    }

    private void login(String id, String pwd) {
        getLoginHander().login(id, pwd);
    }

    private LoginHandler getLoginHander() {
        MyApplication app = (MyApplication) getApplication();
        return app.getLoginHandler();
    }
}
  • activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:paddingLeft="@dimen/activity_horizontal_margin"
              android:paddingRight="@dimen/activity_horizontal_margin"
              android:paddingTop="@dimen/activity_vertical_margin"
              android:paddingBottom="@dimen/activity_vertical_margin"
              android:orientation="vertical"
              tools:context=".MainActivity">

    <EditText
        android:id="@+id/a_main_idEt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Your id"/>

    <EditText
        android:id="@+id/a_main_pwdEt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:password="true"
        android:hint="Your pwd"/>

    <TextView
        android:id="@+id/a_main_titleTv"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/a_main_loginBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Login"/>
</LinearLayout>

이제 Run MainActivityTest(Shift + F10)를 통해 테스트가 가능하다.

끝.

참조1
참조2

Android – Kakaolink 활용하기

kakaolink를 활용하여 특정 문자열 서드파트 어플리케이션에 전달하고자 한다.

상황 예)
전송자 : 카톡을 활용하여 친구에게 메시지를 보낸다. “사전에서 ‘apple’이라는 걸 검색해봐~”
수신자 : 카톡에서 받은 메시지와 함께 사전 검색 버튼이 생성된다. 버튼을 누르게 되면 사전앱이 실행되고 자동으로 검색되게 된다.

kakalink

위와 같은 상황에서 kakaolink를 적극적으로 활용할 수 있다.

  • 어플리케이션 호출 및 문자열 데이터를 받기 위한 권한 부여

    strings.xml 파일

    // key 선언
    <string name="kakao_app_key">000000000000000000</string>
    <string name="kakao_scheme">kakao000000000000000000</string>
    <string name="kakaolink_host">kakaolink</string>

AndroidManifest.xml

         <activity
            android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/kakao_scheme" android:host="@string/kakaolink_host" />
                <data android:scheme="kakao000000000000000000" />
            </intent-filter>
        </activity>
  • Share Mehtod 작성
        try {
            final KakaoLink kakaoLink = KakaoLink.getKakaoLink(a);
            final KakaoTalkLinkMessageBuilder kakaoTalkLinkMessageBuilder = kakaoLink.createKakaoTalkLinkMessageBuilder();
            AppActionInfo aai = new AppActionInfo(AppActionInfo.ACTION_INFO_OS.ANDROID, AppActionBuilder.DEVICE_TYPE.PHONE, "params=test", "referrer=kakaotalklink");
            AppActionBuilder aab = new AppActionBuilder().addActionInfo(aai);
            kakaoTalkLinkMessageBuilder.addAppButton("Open the app", aab.build());
            final String linkContents = kakaoTalkLinkMessageBuilder.build();
            kakaoLink.sendMessage(linkContents, a);
        } catch (KakaoParameterException e) {
            e.printStackTrace();
        }
  • 문자열 데이터를 받는 Activity
         Uri uri = getIntent().getData();
         if (uri != null){
              String param = uri.getQueryParameter("params");
              // do something              
         }

Android Kakaolink Ref
Android kakaolink guide

Android – Gradle을 이용하여 Android Library 만들기

Gradle을 이용하여 Android Library 만들기

Android Studio에서 새로운 프로젝트를 만들어준다. 이것은 실제 라이브러리 프로젝트가 아니라, TDD를 위한 껍대기라고 생각하면 된다.

이제 라이브러리 프로젝트를 만들어보자. New > Module로 새로운 라이브 프로젝트를 만들자.

Build 하기

$ gradle <lib>:build

만약 라이브러리 이름이 util이라면 아래와 같은 Command를 입력해준다.

$ gradle util:build

아무 이상없이 잘 빌드 되었다면 아래와 같은 결과를 확인 할 수 있다.

... 
:util:preCompileDebugUnitTestJava
:util:compileDebugUnitTestJava UP-TO-DATE
:util:compileDebugUnitTestSources UP-TO-DATE
:util:mockableAndroidJar
:util:assembleDebugUnitTest
:util:testDebug
:util:preCompileReleaseUnitTestJava
:util:compileReleaseUnitTestJava UP-TO-DATE
:util:compileReleaseUnitTestSources UP-TO-DATE
:util:assembleReleaseUnitTest
:util:testRelease
:util:test
:util:check
:util:build

BUILD SUCCESSFUL
$ cd util/build/outputs/aar/

위 폴더에서 util-debug.arr, util-release.arr를 확인 할 수 있다.

Depoloy 시키기

안드로이드 프로젝트의 /libs 폴더 안에 추가하자.
그리고 build.gradle파일을 아래와 같이 추가하자.

dependencies {
    compile 'package.name.of.your.aar:util@aar'
}

repositories{
    flatDir{
        dirs 'libs'
    }
}

추가가 정확히 되었다면 Tools > Android > Sync를 누르면 모든 작업이 완료된다.

AAR Format에 대해서

‘aar’ 번들은 Android Library Project에서 사용되는 배포 바이너리 파일이다.

파일 확장자는 항상 .arr로 끝나며, maven artifact 타입으로 잘 알려져있다. 사실 이 파일은 zip파일이며 아래와 같은 구조를 가진다.

  • /AndroidManifest.xml (mandatory)
  • /classes.jar (mandatory)
  • /res/ (mandatory)
  • /R.txt (mandatory)
  • /assets/ (optional)
  • /libs/*.jar (optional)
  • /jni//*.so (optional)
  • /proguard.txt (optional)
  • /lint.jar (optional)

참고

안드로이드 > strings.xml 파일에 공백 추가하기

strings.xml 파일을 이용하여 문자열을 선언하다 보면 가끔 첫 문자의 시작이나 끝 문자에 ‘  ‘공백이 추가가 필요할 때가 있다. 하지만 위와 같이 ‘ ‘ 공백을 추가하게 되면 컴파일러가 이를 제거 해버린다.

위와 같은 문제를 해결하기 위해 유니코드를 활용하면 문제를 해결할 수 있다.
‘  ‘ >  을 이용하여 처리 할 수 있다.

 참조 자료 :  http://stackoverflow.com/questions/10862975/how-to-put-space-character-into-a-string-name-in-xml

Android – 쉽게 커스텀 폰트 사용하기

Calligraphy Library


쉽게 커스텀 폰트를 설정 할 수 있는 라이브 러리이다.

설치하기

메이븐을 이용한 설치

OR

그래들 의존성으로 설치하기

dependencies {
    compile 'uk.co.chrisjenx:calligraphy:2.0.0'
}

폰트 경로는 assets/fonts/에 지정 되어 있어야 한다.

커스텀 속성

“와 같이 사용할 수 있다.

환경 설정

CalligraphyConfig를 Application클래스 안에 사용함으로 써 기본 폰트를 설정할 수 있다.

불행이도 Activity#attachBaseContext(Context)후에 Activity#onCreate(Bundle)이 호출 되기 때문에, 그전에 정의 되어야 한다.

protected void onCreate() {
    super.onCreate();
    CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
                            .setDefaultFontPath("fonts/Roboto-RobotoRegular.ttf")
                            .setFontAttrId(R.attr.fontPath)
                            .build()
            );
    //....
}

Note: CalligraphyConfig를 구지 설정할 필요 없다. 하지만 설정하지 않는다면 기본값은 사용되지 않으면 R.id.fontPath가 사용된다.

Context안에 넣는 방법

Activity Context를 감싸주어라:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
}

사용법

TextView에 커스텀 폰트 설정하기

<TextView
    android:text="@string/hello_world"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    fontPath="fonts/Roboto-Bold.ttf"/>

쓰다 보니 너무 길어서 링크를 남긴다.