안드로이드에서 Gson을 이용해서 서버에서 받은 Json 문자열을 파싱 할 때

필드 중에 Date 타입이 있다.


Gson에서 사용하는 날짜 포맷을 "yyyy-MM-dd HH:mm:ss:SSS z Z" 로 해두고, 서버쪽도 같은 포맷으로 날짜를 문자열로 변환하도록 해두었다.


서버의 타임존을 UTC로 사용하다가 편의상 KST(한국 표준시)로 변경해서 사용하니

받은 날짜의 문자열의 포맷은 아래처럼 된다.


"2014-03-15 21:07:32:221 KST +0900"


정확하게 멀쩡해 보이는 이 문자열을 Date 타입으로 변환하려면 오류가 발생한다.

Date 포맷이 아니라고 한다.

문제는 타임존을 의미하는 KST 부분,

까닭은 안드로이드에서 KST 타임존을 지원하지 않는다.


시간 설정을 한국, 서울 시간으로 해두어도 GMT로 적용되어 간다.


TimeZone.getAvailableIDs()나, TimeZone.getAvailableIDs(int rawOffset)을 이용해서 확인해보고 사용할 것을 그랬다. 라는 생각은 지금에서야 드는 생각이고, 저 예외가 생길 때는 도대체 정말 멀쩡한 문자열이 파싱이 안되서 미치는 줄 알았다. KST가 안되는 줄 꿈에도 몰랐다.

안드로이드에서 테스트하기 귀찮아 그냥 자바 프로젝트에서 같은 문자열을 Date 객체로 변환했을 땐 아~주 문제 없이 잘 파싱이 되었으니깐 말이다.


Moneycomb 진행하면서 시간의 표현에 대해서 많이 배우고 있다.


Eclipse 기반의 ADT를 사용하고 있는데, 아래와 같이 오타를 냈다.


android:id="@+iv/ivAdPic"


+id로 해야하는 것을 오타로 +iv로 한 탓.

이렇게 된 걸 모르고 당최 R.id.ivAdpic이 잡히지 않아서 찾다보니 이유가 저 오타였다.


헌데 안드로이드에서 xml을 파싱하여 R 파일을 생성할 때 오타난 부분은 문법이 +로만 시작하는 문자열이면 되는지

R파일을 보니 iv 정적 클래스가 추가되어있다. xml에서 컴파일 오류를 알려줬다면 금방 알 수 있었을 테지만, 생각지 못한 부분이라 시간을 좀 잡아 먹은 듯


Android SDK Tool 20 버전 이상부터 Library Project의 AndroidManifest.xml 의 내용을 애플리케이션 프로젝트의 Manifest에 병합할 수 있게 되었다 (관련 링크 : http://tools.android.com/download/adt-20-preview)


  • Build System
    • Automatic merging of library project manifest files into the including project's manifest. Enable with the manifestmerger.enabled property.


이전에는 Library Project의 매니페스트에 서비스나 액티비티, 퍼미션 등을 등록하더라도 해당 Library Project를 사용하는 애플리케이션 프로젝트에는 아무런 적용이 되지 않았는데, ADT 20 부터는 이를 활용할 수 있게 된것이다.


지금 소마에서 개발하고 있는 Salina의 안드로이드 SDK에서 퍼미션이나, 서비스 사용을 위해서 애플리케이션 프로젝트에서 이래저래 설정해줘야 할 것들이 많았는데, 이것으로 SDK의 적용이 아주 쉬워졌다. 라이브러리를 개발하는 입장에서는 아주 큰 이점이 아닌가 싶다.


라이브러리 프로젝트와 애플리케이션 프로젝트의 Manifest의 내용이 중복되는 경우의 충돌은 확인해 봐야할 듯 하다.


적용방법은 간단하다.  프로젝트의 설정파일(project.preference)에서 manifestmerger.enabled=true 만 추가해주면 적용된다.

Salina Project에서 RatingBar가 아래 같은 모습으로 들어가야 하는데 기본 크기는 너무 크다






그렇다고 그냥 RatingBar의 layout_width, layout_height의 크기를 줄여버리면 이런 모습이 되어버린다.


ㅡㅡ...




검색 결과 중 대부분이 RatingBar를 커스터마이징 하기 위해서 style xml을 만들어내는 것이었는데,

(http://blog.naver.com/PostView.nhn?blogId=telsome&logNo=90094111681)

RatingBar를 커스터마이징 할 수도 있는 거지만 크기만 변경하는 되는 문제여서 더 간단한 것을 찾다 보니 

StackOverFlow에서 답을 찾을 수 있었다.


>> StackOverFlow : Decrease the size of Rating Bar in android 2.1


RatingBar의 xml에 속성에 style="?android:attr/ratingBarStyleSmall" 를 추가하면 보다 작은 크기로 적용이 된다.


<RatingBar
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    style="?android:attr/ratingBarStyleSmall"/>


다만 정확히 원하는 크기로 변경하기 위해서는 커스터마이징을 해야한다.

이미지뷰를 화면에 꽉 채우기에 앞서 뷰의 영역에 대해 먼저 설명하겠습니다.



그림이 다소 난잡하지만, 하나의 뷰는 크게 세 가지 영역으로 볼 수 있습니다.

뷰가 실제로 보이는 크기와는 상관이 없지만 다른 뷰와의 거리를 조절하는 외부 여백 margin

눈에 보이는 크기에 포함되는 내부 여백 padding,

전체 뷰 크기에서 위의 두 여백을 제외하고 내용이 들어가는 부분으로 나뉘어집니다.


margin과 padding은 둘다 여백이지만 뷰에 배경색상을 지정했을 때 margin 영역에는

배경색상이 지정되지 않고 공간만을 차지하고, padding 부분에는 배경색상이 적용됩니다.


왜 이 이야길 꺼냈느냐 하면, 액티비티에 이미지뷰를 삽입하려면 이미지뷰를 둘러싸고 있는 레이아웃이

padding의 값이 0이 아닌 경우가 있기 때문에 이미지뷰를 아무리 키운다고 한들 여백이 생기기 때문입니다.


IDE (Eclipse나 IntelliJ 기반의 Android Studio)에서 새로운 레이아웃 XML을 자동으로 생성하면

기본 레이아웃은 RelativeLayout이고 요런 속성이 주렁주렁 달려서 자동으로 xml 문서가 생성됩니다.

android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"


저렇게 자동으로 생성된 xml 레이아웃 파일의 경우 저 부분을 제거해주어야 원하는 대로 화면에 꽉 찬 이미지뷰를 만들 수 있습니다.



여백에 관한 얘기는 여기까지 하고, 이미지뷰에서 이미지를 채우기 관련 속성인 scaleType에 대해 알아보겠습니다.

scaleType 속성이 가질 수 있는 값은



위의 그림과 같이 matrix, fitXY, fitStart, fitCenter, fitEnd, center, centerCrop, centerInside 여덟가지입니다.


위 속성에 관해 녹두장군님의 포스트(http://mainia.tistory.com/473)에서 설명이 잘 되어있어 링크로 대체하도록 하고, 화면에 이미지를 맞추기 위해서는 scaleType의 값으로 fitXY를 사용하면됩니다.


위의 내용대로 한다면 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"
    tools:context=".IntroActivity" >

    <ImageView 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/salina_community_intro"
        android:scaleType="fitXY"/>

</LinearLayout>


추가적으로 Manifest에서 Activity의 theme 속성 수정을 통해서 타이틀바의 유무, 상태표시바 유무(풀스크린)를 수정할 수 있습니다. 어찌보면 별 내용도 없는 것을 횡설수설 괜히 길게 썼네요. 도움이 되셨길 바랍니다~!  :)

현재 진행 중인 Salina 프로젝트에서 나름 국제화를 적용하겠다고 상수 스트링을 values 리소스로 빼두고

final 키워드를 제거 후 멤버 변수 위치 그대로 두고 getString(int resourceId)를 호출하니 NullPointerException 발생


프로그래밍계의 지식인  StackOverflow느님께서 역시 해결책을 주셨다.


원인은 액티비티가 초기화되기 전에 리소스를 사용하려고 하면 NullPointerException이 발생할 수 있다고 한다.


즉 액티비티에서 사용하는 getString()은 Resources 객체의 getString()을 래핑한 메서드이고.

Resources는 Context 객체로부터 얻을 수 있는데, getString()을 풀어보면

getContext().getResources().getString()이 되는 셈이다.


따라서 해결책은 멤버 필드에서 바로 초기화를 하는 대신 onCreate 이 후 시점에 해야한다.

그냥 onCreate 안에서 하면 된다.

혹은 Application 클래스를 상속한 커스텀 Application을 만들고 거기에 static 메서드를 이용해서 컨텍스트를 통해 리소스를 가져오는 방법이다.

액티비티는 해당 액티비티가 onCreate 되어서야 초기화 후에 context를 갖지만 애플리케이션 단위에서는 처음 앱이 실행될 때 정보를 가지고 있기 때문에 가능하다.






자바 웹 프로그래밍을 하면서 ORM을 당연하게 사용해왔는데,

안드로이드에서 SQLite DB 프로그래밍을 하려니.. ORM 쓰던게 습관이 되어있어서인지

귀찮은 점이 한두가지가 아니네요.


먼저 ORM에 대해 간략하게 소개하자면 Object Relational Mapping으로 객체와 릴레이션(RDB의 테이블)간에 매핑을 해 주는 역할을 합니다. 쉽게 말하자면 Book 이라는 객체가 있다면 이 객체를 ORM을 통해 RDB에 넣거나(Insert), 가져오거나(Select), 지우거나(Delete), 고칠 수(Update)도 있고, 기본적인 CRUD 외에도 스키마를 다루거나 조인 연산등 RDB의 기능을 객체지향적으로 다룰 수 있도록 도와줍니다.



ORM

이미지 출처 : 4 Benefits of Object-Relational Mapping (ORM)


SQLite를 대상으로 하는 안드로이드 기반 ORM 라이브러리를 몇개 찾아보았습니다. 아래와 같은 것들이 있네요.

  1. ActiveAndroid(https://www.activeandroid.com/)
  2. SqliteORM(https://github.com/kremerk/SqliteORM/wiki)
  3. ORMLite(http://ormlite.com)
  4. Storm(https://code.google.com/p/storm-gen/)
  5. Green Dao(http://greendao-orm.com)
  6. Sugar ORM(https://github.com/satyan/sugar)


글 제목에서도 알 수 있지만 Sugar ORM이 간단하게 쓰기에는 쉽게 쓸 수 있도록 되어있네요. 모두 사용해 볼 수는 없어서 각 라이브러리들의 Getting Start에서 사용 방법을 눈으로만 살펴봤는데, DB 핸들링 관련 코드를 만들기 위한 Generater와 사용을 위한 라이브러리가 따로 있는가 하면, 빌드 패스에 이것 저것 추가하고 설정도 하는 것도 있고, 이래 저래 사용하기 복잡해서 좀 귀찮아도 그냥 Helper 구현해서 쓰고 말지 하는 생각이 드는 것들이 대부분이었네요. (사실 새로운 걸 익힌다는 것에 대한 귀찮음이 맞지만요 :D )


어쨌거나 Sugar ORM이 개중에서는 가장 쓰기 쉽도록 되어있네요.

간략하게 사용 순서를 풀어보면

1. 안드로이드 프로젝트 libs 폴더에 라이브러리를 추가한다.

2. 매니페스트에 Application 클래스를 설정한다(보통은 설정되어있지 않고 디폴트로 사용합니다.)

3. 매니페스트에 meta-data 태그를 이용하여 4개의 프로퍼티를 추가한다.

4. 쓴다.






코드로 써 봐야 아는 것에 구구절절 글을 늘어놓는 것 보다 백문이 불여일견 백견이 불여일행이라 하였으니 해보도록 하겠습니다. 개발 환경은 Windows 7, Eclipse Indigo + Android SDK Plug-in입니다.


가장 먼저 Sugar ORM GitHub 다운로드 페이지에서 라이브러리 파일을 다운로드 받습니다.

https://github.com/satyan/sugar/blob/master/dist/sugar-1.1.jar

(글을 쓰는 13.03.04 기준 7개월 전에 업데이트 되었네요)


그런 다음 이클립스를 켜고 새 안드로이드 애플리케이션 프로젝트를 생성해 줍니다.



프로젝트 이름이나 패키지는 본인의 입맛에 맞게 설정하시면 되겠습니다.

다만 Package Name은 기억해 두도록 하세요. 저는 nnoco.example.sugar 로 기본 패키지 경로를 정했습니다.

나중에 Manifest.xml 파일에 속성값으로 쓸 일이 있습니다.


Next -> Next -> ... Finish를 하면 프로젝트가 짠! 하고 만들어졌습니다.


ADK 플러그인 버전에 따라서 libs 폴더를 자동으로 생성해주기도 하고, 없는 경우도 있는데요, libs 폴더는 기본적으로 안드로이드 패키지에서 라이브러리가 있는 클래스패스로 인식을 합니다. 외부 라이브러리 파일의 경우 이곳에 복사하시면 클래스패스에 따로 추가하지 않으셔도 자동으로 잡히게 됩니다.

따라서 프로젝트 루트에 libs가 없는 분은 libs 폴더를 하나 추가해주시면 되겠습니다. 그럼 아래와 같은 프로젝트 구조가 보이게 됩니다. (환경에 따라 조금씩 다를 수 있지만 여기서는 libs 폴더만 있으면 됩니다.)



그럼 저 libs 폴더 안에 프로젝트를 만들기 전에 다운로드 받은 sugar-1.1.jar 파일을 복사해서 넣어줍니다.




여기까지 되었다면 이제 Manifest.xml 파일을 편집해 보겠습니다.

해야할 것은 Custom Appication 클래스를 지정하는 것과, meta-data의 추가입니다.


<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="nnoco.example.sugar"

    android:versionCode="1"

    android:versionName="1.0" >


    <uses-sdk

        android:minSdkVersion="8"

        android:targetSdkVersion="15" />


<!-- Custom Application 지정 -->

    <application

        android:name="com.orm.SugarApp"

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

        <activity

            android:name=".MainActivity"

            android:label="@string/title_activity_main" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

        <!-- 메타 데이터 추가 -->

        <meta-data android:name="DATABASE" android:value="sugar_example.db"/>

        <meta-data android:name="VERSION" android:value="2"/>

        <meta-data android:name="QUERY_LOG" android:value="true"/>

        <meta-data android:name="DOMAIN_PACKAGE_NAME" 

                      android:value="nnoco.example.sugar"/> <!-- 본인의 패키지로 설정-->

    </application>


</manifest>



저렇게 두 부분만 (라인으로는 5줄!) 추가하면 사용할 준비는 끝입니다.

이제 사용 해보도록 하겠습니다. 만들고자 하는 엔티티는 다음과 같습니다.



간단합니다. 책 엔티티로 제목(title)과 저자(author)를 가지고 있습니다.

여기에 맞는 모델 클래스를 만들어야겠죠? 원하는 위치에 Book 클래스를 하나 만들어보도록 하겠습니다.

모델 클래스를 만들 때 Sugar ORM을 사용하면 하나의 제약사항이 있습니다. SugarRecord를 상속해서 구현해야 한다는 점과, Context를 매개변수로 받는 생성자가 필요합니다. 사실 SugarRecord를 상속 받는 건 제약이라기 보다 편의쪽에 가깝다고 봐야겠네요.




public class Book extends SugarRecord {

private String title;

private String author;


public Book(Context context) {

super(context);

}


public Book(Context context, String title, String author) {

super(context);

this.title = title;

this.author = author;

}


public String getTitle() {

return title;

}


public void setTitle(String title) {

this.title = title;

}


public String getAuthor() {

return author;

}


public void setAuthor(String author) {

this.author = author;

}

}


Context 객체를 받는 생성자와 title, author를 포함해서 받는 생성자, getter/setter를 만들어준 모습입니다.



여기까지 되었으면 메인 액티비티로 이동해서 onCreate 메서드에서 간단하게 테스트를 해보도록 하겠습니다.

// Book 인스턴스 생성

Book book = new Book(this, "논어", "공자");

        

// DB에 저장

book.save();

        

// 수정

book.setTitle("論語");

book.save();

        

// DB로부터 ID 가져오기

long id = book.getId();

        

// ID를 이용해서 객체 가져오기

Book foundBook = Book.findById(Book.class, id);

        

// 로그로 확인

Log.d("Found Book Info", foundBook.getTitle() + ", " + foundBook.getAuthor());

        

// 삭제

book.delete();


api는 참 쉽죠? save()는 없다면 생성하고, 있다면 수정합니다. delete()는 삭제구요.

static 메서드에는 find(...), 나 findById(...)과 같이 select의 기능을 하는 메서드가 있고,

listAll(...) 테이블의 모든 레코드를 가져와 리스트로 반환하는 메서드,

deleteAll(...) 과 같이 테이블의 모들 레코드를 삭제하는 메서드가 있습니다.


이외에도 1:1 관계의 표현, 1:N관계의 표현등에 대한 내용이 있지만 간단하게 소개하는 글이므로 여기까지 쓰도록 하고

더 자세한 내용은 Sugar ORM의 위키를 통해 확인하실 수 있습니다.(http://satyan.github.com/sugar/getting-started.html)


레이아웃을 어느정도 만들어서 EditText 뷰에 속성값을 넣고 추가하고 리스트뷰에서 보여주고 하는 식으로 하려다가 단순한 기능 때문에 오히려 다른 부분(리스트뷰 어댑터 만들고, 이벤트 추가하는 등의)의 작업이 많아지면 의미없는 일이라고 생각하여 그 부분은 제외하였습니다. 

리스트 뷰를 사용하여 DB내용을 확인하는 예제는 첨부하여 올리니 확인하실 분들은 다운로드 받으셔서 확인하시면 됩니다.


Android ORM Test.zip





  1. 에코지오 2013.06.18 11:06 신고

    안녕하세요. Sugar ORM에 좀 심각한 버그가 있어 알려드립니다.
    보통은 DB처리 작업은 백그라운드에서 처리를 하는게 맞죠. 올려주신 소스중 MainActivity의 fillFromDB() 메소드를 백그라운드에서 처리되도록 아래와 같이 수정해서 테스트해보시기 바랍니다.

    private void fillFromDB() {
    new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... params) {
    books.addAll( Book.listAll(Book.class));
    return null;
    }

    protected void onPostExecute(Void result) {
    adapter.notifyDataSetChanged();
    }
    }.execute();
    }

    AsyncTask를 사용하는 경우 Sugar ORM이 이렇게 에러를 토해냅니다.

    FATAL EXCEPTION: AsyncTask #1
    java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:278)
    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
    at java.lang.Thread.run(Thread.java:856)
    Caused by: java.lang.ExceptionInInitializerError
    at java.lang.Class.classForName(Native Method)
    at java.lang.Class.forName(Class.java:217)
    at com.orm.SugarDb.getDomainClass(Unknown Source)
    at com.orm.SugarDb.getDomainClasses(Unknown Source)
    at com.orm.SugarDb.createDatabase(Unknown Source)
    at com.orm.SugarDb.onCreate(Unknown Source)
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:165)
    at com.orm.Database.openDB(Unknown Source)
    at com.orm.SugarRecord.find(Unknown Source)
    at com.orm.SugarRecord.listAll(Unknown Source)
    at nnoco.android.orm.test.MainActivity$1.doInBackground(MainActivity.java:44)
    at nnoco.android.orm.test.MainActivity$1.doInBackground(MainActivity.java:1)
    at android.os.AsyncTask$2.call(AsyncTask.java:264)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    ... 5 more
    Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:121)
    at android.support.v4.content.ModernAsyncTask$InternalHandler.<init>(ModernAsyncTask.java:466)
    at android.support.v4.content.ModernAsyncTask$InternalHandler.<init>(ModernAsyncTask.java:466)
    at android.support.v4.content.ModernAsyncTask.<clinit>(ModernAsyncTask.java:75)
    ... 19 more


    Sugar ORM개발자가 context와 클래스로더에 대해서 고민이 부족해서 발생한 문제로 보입니다.

    • 마음이 뛰다 2013.06.19 06:05 신고

      소스를 살펴보려고 GitHub에 갔더니 어제 날짜로 1.2 버전으로 업데이트 되었네요.
      상세한 변경사항은 알 수 없으나 말씀하신 내용은 수정된 듯 합니다.

HTTP 요청 메시지

Full request = 

요청 라인(Request-Line) : <요청 메서드> <요청 URI> <HTTP 버전>

일반헤더(General-Header)

요청헤더(Request-Header)

엔티티헤더(Entity-Header)

CRLF

엔티티본문(Entity-Body)


HTTP 메서드 종류

OPTIONS

GET - REST API에서 자료를 조회하는 기능

HEAD

POST - REST API에서 자료를 생성하는 기능

PUT - REST API에서 자료를 생성/수정하는 기능 (없다면 생성)

DELETE - REST API 자료를 삭제하는 기능

TRACE

CONNECT


HTTP 응답 메시지

Full-Response =

상태라인(Status-Line) : <HTTP 버전> <상태 코드><이유문구> CRLF

일반헤더(General-Header)

응답헤더(Response-Header)

엔티티헤더(Entity-Header)

CRLF

엔티티본문(Entity-Body)\


URL의 구성요소

scheme://<user>:<password>@<host>:<port>/<url-path>?query_string$fragment_id

Scheme : 'http', 'ftp'와 같이 프로토콜을 나타낸다

Host : DNS의 인터넷 주소 규정에 따라 서버를 가리킨다.

Port: TCP 포트번호를 나타낸다. Http 프로토콜의 경우 별도 포트번호를 명시하지 않는다면 디폴트는 80

Path : 구체적인 자원의 위치

Query : 서버에서 작동하는 애플리케이션에 전달되는 매개변수

Fragment id : 참조 또는 자원의 상세한 부분을 의미

이클립스 안드로이드 레이아웃에 EditText 뷰만 넣으면 아래와 같은 예외가 발생하면서

java.util.LinkedHashMap.eldest()Ljava/util/Map$Entry;

Exception details are logged in Window > Show View > Error Log

GUI 화면에서는 제대로 레이아웃이 보이지 않는 문제가 발생했다.


모놀로그님의 글을 통해 1차적인 문제는 해결했으나, EditText에 멀티라인이 적용되지 않는 것이다.


scrollHorizontally="false" 속성을 추가해 보기도 하고

singleLine="false" 로 해보기도 하고

lines="5" 와 같이 line 수를 하드코딩도 해보았으나 모두 적용이 되지 않는다.


1차적인 해결 방법은 EditText에 inputType="textNoSuggestions" 속성을 추가하는 것이었고, 그로 인해 파싱 오류는 없어졌다. 허나 textNoSuggestion 속성값은 IME의 엔터키에 엔터를 완료로 바꾸는 것이라(좀 더 정확히는 사전 기반의 단어 추천을 하지 않게 하는 기능이나 외부적으로는 IME에 완료 버튼을 나타나게 함) EditText 뷰 자체가 싱글라인으로 인식이 된다.


혹시나 싶어 inputType 속성의 값을 textMultiline으로 하니 원래 지정한 대로 scrollHorizontally 속성으로 수평 방향 스크롤도 없어졌으며 Gson에서 pretty print로 얻은 JSON 스트링도 정상적으로 줄바꿈이 되어 표시가 된다.


그러나 파싱 오류는 다시 발생해서 레이아웃 확인을 Graphical Layout에서 확인할 수는 없게 되었다.

textNoSuggestions 말고 근본적인 파싱 오류의 원인을 찾아봐야겠다.





참고 : inputType 속성 값에 대한 특성은 서동규님의 블로그 글에서 확인할 수 있음

+ Recent posts