반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

[Android] Optional SQLite Tutorial

2016. 10. 23. 04:43 | Posted by 솔웅


반응형

Optional SQLite Tutorial

Sunshine 앱에서는 날씨 정보를 저장하기 위해 SQLite database를 사용할 것입니다. SQLite에 대한 기본적인 이해가 있어야 합니다. 그리고 기본적인 명령어들도 익혀야 합니다. 이 글은 SQL 데이터베이스에 초보인 사람들 혹은 refresher를 위한 글입니다.


Introduction

SQLite은 관계형 데이터베이스 관리 시스템 입니다. SQL (Structured Query Language)을 사용합니다. C++ 라이브러리처럼 Android OS에 패키지로 있어서 각 앱들마다 private database를 가질 수 있습니다 SQL은 create, search 그리고 maintain database 등에 이용됩니다. 이 글에서는 SQL의 신택스와 사용법에 대해 다룹니다. 그리고 어떻게 작은 규모의 데이터베이스를 생성하고 관리하는지에 대해서도 다룹니다. 여기서 다루는 commands들은 Sunshine에서 사용될 SQL과 비슷합니다.

Get SQLite

    1. 이곳에서 SQLite을 다운받아 설치하면 컴퓨터에서 사용할 수 있습니다.
    http://sqlite.org/download.html
    SQLite Installation

    2. 설치 후 윈도우 커맨드 창을 열어서 데이터베이스를 저장할 폴더로 이동합니다. sunshine.db라는 데이터베이스를 아래와 같이 설치 합니다.
    sqlite3 sunshine.db

    3. 모든 commands들을 보려면 이렇게 타입 합니다.
    .help

    4. 그중에 모든 데이터베이스를 보여주는 명령어가 있습니다. 현재로서는 sunshine.db에 대한 정보가 보여질 겁니다.

    .databases


    앱에서 여러분은 여러개의 데이터베이스들을 만들 수 있습니다. Sunshine 엡에서는 하나의 데이터베이스만을 가질 겁니다. 이 데이터베이스 안에는 여러개의 테이블을 만들 수 있습니다.


Create A Database Table

  1. A table is a collection of rows and columns like a spreadsheet. Use the CREATE TABLE statement to create a new database table called “weather.” Each row will be one day’s worth of weather data. It should have 6 columns of data: ID, date, min temperature, max temperature, humidity, and pressure.

    In the CREATE TABLE statement, each column definition is separated by commas, where you provide the column name and datatype for that column. We also specify that the column should be non-null. We specify the _id column to be the primary key and it’s an integer.

    CREATE TABLE weather( _id INTEGER PRIMARY KEY, date TEXT NOT NULL, min REAL NOT NULL, max REAL NOT NULL, humidity REAL NOT NULL, pressure REAL NOT NULL);

    The list of possible SQLite data types is a useful resource, or you can see this tutorial.

    Note: SQLite keywords, such as CREATE TABLE or PRIMARY KEY, are capitalized for ease of readability to distinguish them from the table and column names that we’ve selected, but you can lowercase the keywords if you want.

    Note: This is not the full table you’ll be using in Sunshine, this is just a simpler version of the table.

  2. Use this command to list out all tables. Ensure that the weather table was created.

    .tables

  3. Use a SELECT statement to return out all rows in the weather table. The * is a symbol that means “all of the columns”. At this time, nothing will be returned because the table is created, but there is no data in the table yet.

    SELECT * FROM weather;

  4. At any point, you can find out the schema of how the tables were created in the database

    .schema

Insert rows

  1. Use an INSERT statement to insert a new row of data into the weather table. The following INSERT statement inserts a row into the weather table for June 25th, 2014, which had a low of 16 degrees, a high of 20 degrees, 0 humidity and 1029 pressure. The _id of this row is 1.

    INSERT INTO weather VALUES(1,'20140625',16,20,0,1029);

  2. Query for all rows in the weather table, and you should see the one row of data you just inserted.

    SELECT * FROM weather;

  3. To have the column name be printed out as well (for easier readability as to what value corresponds to which column), turn the header on. Then do the query again.

    .header on
    SELECT * FROM weather;

  4. Experiment by inserting another 3 rows of data into the weather table. INSERT INTO weather VALUES(2,'20140626',17,21,0,1031); INSERT INTO weather VALUES(3,'20140627',18,22,0,1055); INSERT INTO weather VALUES(4,'20140628',18,21,10,1070);

    Query for all rows to verify they were inserted properly.

    SELECT * FROM weather;

Query rows

  1. Practice doing queries where you provide a selection WHERE clause to narrow down the number of rows that are returned in the result. Always remember the semicolon at the end of a statement!

    For all possible SQLite operators, see this link.

    This query returns rows from the weather table where the date column exactly equals the 20140626.

    SELECT * FROM weather WHERE date == 20140626;

  2. This query returns rows from the weather table where the date column is between 20140625 and 20140628. However, all columns are not returned, we just return the 4 specified columns (_id, date, min, and max) of the rows that match the query.

    SELECT _id,date,min,max FROM weather WHERE date > 20140625 AND date < 20140628;

  3. This query returns rows where the minimum temperature is greater than or equal to 18. Based on those matching rows, we order them based on increasing (also known as ascending or “ASC” for short) max temperature. The first row of the result that is printed out to the command line will be the row (with min temperature >= 18) with max temperature that is lowest out of all rows, so that subsequent rows will have higher max temperature.

    SELECT * FROM weather WHERE min >= 18 ORDER BY max ASC;

Update rows

  1. You can also update existing rows in the database with an UPDATE statement. This statement updates the weather table by setting the minimum temperature to be 0 and maximum temperature to be 100 for rows where the date is greater than 20140626 but less than 20140627.

    UPDATE weather SET min = 0, max = 100 where date >= 20140626 AND date <= 20140627;

    When you print out the whole weather table again, you can see that 2 rows were changed.

    SELECT * FROM weather;

Delete rows

  1. Use a DELETE statement to delete rows from a database table that match the given selection clause. In this case, we delete any rows from the weather table where humidity is not equal to 0.

    DELETE FROM weather WHERE humidity != 0;

Add columns

  1. If you have released a version of your app to users, and then decide you need to change the database schema, such as adding columns, then you’ll need to upgrade your database. You can alter existing tables, by using the ALTER TABLE command.

    Note: In general, you shouldn’t alter a table to remove a column because you’re deleting data and other tables could depend on that column. Instead you can just null out all values in that column.

    This statement alters the weather table by adding another column to the table called description, which will always be non-null and contain text. It will also default to the value ‘Sunny’ if no value is provided. In reality, you would choose a more reasonable default, but this is just for example purposes.

    ALTER TABLE weather ADD COLUMN description TEXT NOT NULL DEFAULT 'Sunny';

    Verify that the new description column exists when you query all rows and all columns.

    SELECT * FROM weather;

Delete table

  1. Delete the weather table by using the DROP TABLE command. Verify there are no more tables in the database.

    DROP TABLE weather;
    .tables

These are just the basics. Feel free to play around with SQLite some more. See this link: http://www.sqlite.org/cli.html

When you’re done, enter .quit to exit, and you can move onto the quiz on the next page!

반응형

Udacity 강좌 - Lesson 4 - Activity

2016. 10. 23. 02:12 | Posted by 솔웅


반응형

4과에서는 Activity Persistence Storage (SQLite) 다룹니다.

 

우선 액티비티를 보겠습니다.

 

안드로이드에서는 주로 하나의 화면이 하나의 액티비티 입니다.

 

선샤인 내에서도 여러 화면이 있습니다. 그래서 여러 화면을 다니다 보면 디바이스 내에 여러개의 액티비티가 있게 됩니다.

 

선샤인을 사용하다가 다른 앱들도 사용하게 되는데요. 이러면 앱들의 액티비티들도 디바이스내에서 작동하게 됩니다.

 

디바이스 내에는 하나의 화면만 보이겠지만 뒤에 지금까지 실행했던 화면들이 숨어 있게 됩니다.

 

이럴 화면들을 계속 띄우다 보면 디바이스의 메모리가 부족해 때가 있습니다.

 

사용자게 의해서이건 시스템에 의해서이건 특정 Activity들이 Kill 있습니다.

 

이렇게 액티비티가 화면에 떴다가 Background 있다가 Kill 되고 하는 과정들은 Activity Lifecycle 보면 이해하기 쉽습니다.

 

Activity와 관련해서 좀 더 자세한 사항을 알고 싶으면 이곳에서 보시면 됩니다.



http://developer.android.com/training/basics/activity-lifecycle/starting.html



위 그림에 나와있는 Android Activity Life Cycle을 보겠습니다.
우선 처음에 앱을 시작하면 onCreate가 실행 되서 앱이  Create 됩니다.


그리고 나서 onStart가 실행되고 나면 비로서 화면이 디바이스에 보기게 됩니다.
화면이 보이는 상태가 Active 상태인데요. 이 때 onResume을 거쳐서 Active됩니다.


다음 단계는 Paused 상태인데요.
이 상태는 Alert 화면이 떴다던가 다른 화면이 떴지만 반투명 상태라서 이전 화면이 현재화면 뒤에 보이게 되는 상태입니다.


그러니까 완전하게 눈에 보이지 않게 되는 상황이 아니라 어스름하게라도 보이는 상황인데요.
이때 그 Activity는 onPause를 거쳐서 Paused한 상황이 됩니다.


이 상황에서 다시 화면에 보이게 될 수 있는데요 이때는 onResume을 거쳐서 화면이 활성화 됩니다.
Paused상태에서 다른 화면이 완전히 덮어서 사용자 눈에 보이지 않는 상황이 될 수도 있는데요.
그 때는 Stopped 상태입니다. onStop 과정을 거쳐서 이 상태가 됩니다.


해당 Activity는 눈에 보이지는 않지만 Background에서 계속 살아 있습니다.


즉 일정정도 메모리가 할당 돼 있는 상황입니다.


여기서 다시 이 뒤의 화면을 foreground로 불러올 경우도 있습니다. 이때는 Restart를 하게 됩니다.
이땐 onRestart가 실행 됩니다.


아까 onResume은 Paused 상태에서 실행 되는 것이고 이 onRestart는 Stopped상태에서 실행되는 것입니다.


onRestart가 실행되면 해당 액티비티가 다시 화면에 보이게 됩니다.


onStart를 거쳐서 보이게 되고 onResume을 거쳐서 활성화 됩니다.


한편 Stopped 상태에서 Background에서도 완전히 사라질 수도 있는데요.


즉 메모리에서 해당 Activity가 사라지게 되는 상황은 onDestroy를 거쳐서 발생됩니다.


해당 Activity가 완전히 Destroy되는 거죠.


이 Activity Life Cycle 을 잘 이용해서 적당한 때에 적당한 정보를 저장하고 다시 불러오고 연결을 끊고 재연결하고 하는 작업들을 잘 해야 앱이 무난하게 작동하게 됩니다.


게임을 하다가 상태정보를 저장하지도 않고 Destroy되면 그 다음에 앱을 시작하면 벌써 깼던 stage를 다시 새로 해야 되는 상황이 발생할 수 있으니까요.



그러면 Device 가 Rotation이 될 때는 어떤 과정을 거칠까요?
앱에서 대개 가로 화면과 세로 화면이 다른 경우가 있는데요. 이 화면 전환을 할 때는 어떤 과정을 거칠까요.
 
onPause - onStop - onDestroy - onCreate - onStart - onResume

그 때는 위와 같은 과정을 거칩니다.
 
Memory가 다해 시스템이 강제로 백그라운드 액티비티를 터미네이트 할 경우를 대비해서 관련된 작업을 해 둬야 합니다.
밧데리가 다 돼서 강제로 터미네이트 되는 경우도 있습니다. 이럴 때도 관련된 작업을 해 둬야 합니다.
(onPause 나 onStop 등등에)
 
Sensor Listeners, Location Updates, Dynamic Broadcast Receivers, Game Physics Engine
이런 것들은 onPause 나 onStop 시 disconnect 시키거나 stop 시켜야 합니다.
 


시스템에 의해 강제로 종료될 때를 대비해서 상태 정보는 onPause시에 저장하고 다시 onCreate시에 restore 합니다.


Active -> onSaveInstanceState -> onPause -> Terminated
-> onCreate -> onRestoreInstanceState



여기까지가 Activity와 관련된 이론들 이었습니다.
이후에는 데이터를 저장하는 것과 관련된 내용이 있습니다.


예를 들어 현재 작업하고 있는 Sunshine앱에서...
현재는 날씨를 표시할 때마다 날씨 웹싸이트에 접속해서 화면에 뿌려 줍니다.


하지만 이러면 네트워크에 접속하고 처리하는 과정을 매번 거치게 되서 시간이 많이 걸릴 수 있습니다.
네트워크 사정에 따라서 화면이 아주 늦게 뜰 수도 있구요.


또 밧데리 소모도 더 빨리 됩니다.


해당 서버의 접속량도 아주 많아 질 거고...


비행기 안이라던지 네트워크를 사용할 수 없는 경우에는 아예 날씨를 볼 수가 없습니다.
 


이런 이유로 한번 날씨를 받으면 이것을 저장해 뒀다가 일정기간 사용을 하면 위와 같은 단점들을 극복할 수 있습니다.


 
이럴때 모바일 데이터베이스인 SQLite를 사용하게 됩니다.
자세한 내용은 다음 글에서 이어 나가겠습니다.



반응형

[Android] Settings - 3 -

2016. 10. 13. 05:18 | Posted by 솔웅


반응형

Building a Custom Preference

안드로이드 프레임워크는 여러가지 모양으로 세팅 화면을 꾸밀 수 있도록 다양한 Preference subclass들을 제공합니다. 하지만 어떤 경우 이런 built-in solution 이 아닌 다른 모양으로 꾸며야 될 때가 있습니다. 예를 들어 number picker 나 date picker 등이 있습니다. 이런 경우 custom preference를 만들 수 있는데요. pPreference 클래스나 다른 subclass 를 extend 해서 사용하면 됩니다.

Preference class를 extend 할 때 주의해야 할 점 몇가지가 있습니다.

    사용자가 세팅을 클릭했을 때 보여 질 UI를 정합니다.
    적당한 때에 세팅 값들을 저장합니다.
    Preference를 view에 값들이 나올 때 Initialize 해 줍니다.
    시스템에 의해 request 될 때는 default 값을 줍니다.
    Preference 가 자신의 UI (dialog 같은)를 제공할 때 lifecycle 변화를 컨트롤 하기 위해 state를 저장하고 restore 합니다. (예를 들어 사용자가 스크린을 rotate 할 때)

   
아래 섹션들에서 이른 일들을 하려면 어떻게 해야 하는지를 설명 할 겁니다.


Specifying the user interface


만약 Preference class 를 direct로 extend 한다면 사용자가 해당 아이템을 선택 했을 때 어떤 action이 일어나도록 하기 위해 onClick() 메소드를 implemet 해야 합니다. 대부분의 custom settings는 DialogPreference를 extend 합니다. dialog를 보여야 하니까요. 그러면 일의 진행을 좀 더 간단하게 처리할 수 있죠. DialogPreference를 extend 할 때 그 클래스에서 layout을 생성하는 동안 setDialogLayoutResource()를 호출해야 합니다.


예를 들어 여기 custom DialogPreference 를 위한 constructor 가 있습니다. layout을 정의하고 positive, negative 버튼을 정의 합니다.


public class NumberPickerPreference extends DialogPreference {
    public NumberPickerPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        setDialogLayoutResource(R.layout.numberpicker_dialog);
        setPositiveButtonText(android.R.string.ok);
        setNegativeButtonText(android.R.string.cancel);

        setDialogIcon(null);
    }
    ...
}



Saving the setting's value




세팅에 대한 value들은 Preference class의 persist*() 메소드를 호출하면 어느때든지 저장할 수 있습니다. 예를 들어 값이 Integer 일 경우는 persistInt()를 Boolean 일 경우는 persistBoolean()을 호출하면 됩니다.


Note:  각각의 Preference 는 한가지의 data type으로 정의 됩니다. 그리고 거기에 해당하는 persist*() 메소드를 사용하여야 합니다.


persist를 선택하면 세팅은 extend 한 Preference class에 따라 진행 됩니다. DialogPreference 를 extend 했다면 사용자가 OK 버튼을 눌러서 이 창이 닫힐 때 해당 값을 persist 해야 합니다.


DialogPreference 가 닫힐 때, 시스템은 onDialogClosed() 메소드를 호출합니다. 이 메소드는 boolean argument를 가지고 있습니다. 사용자가 선택한 것이 OK 인지 Cancel 인지를 구분하기 위한 것이죠.
OK 를 누르면 해당 값을 persist 합니다.


@Override
protected void onDialogClosed(boolean positiveResult) {
    // When the user selects "OK", persist the new value
    if (positiveResult) {
        persistInt(mNewValue);
    }
}


이 예제에서는 mNewValue가 class member 입니다. 세팅의 현재 선택 된 값을 가지고 있는 변수죠. persistInt를 호출해서 이 값을 SharedPreference file 에 저장합니다.
(automatically using the key that's specified in the XML file for this Preference).


Initializing the current value

시스템이 화면에 여러분의  Preference를 추가 할 때 onSetInitialValue() 메소드가 호출됩니다. 이 메소드는 이 세팅이 persisted value 인지 여부를 notify 하게 됩니다. 만약 persisted value가 없으면 default value를 제공합니다.

onSetInitialValue() method는 boolean, restorePersistedValue 를 pass 합니다. 세팅에 값이 persisted 됐는지 여부를 알려주죠. true 이면 해당 Preference class의 getPersisted*() 메소드를 호출해서 persisted 된 값을 retrieve 해야 합니다. Integer value 인 경우는 getPersistedInt() 가 되겠죠. 최신의 값을 화면에 표시하려면 제 때애 UI를 update 해 줘야 합니다.

만약 restorePersistedValue가 false 이면 두번째 argument로 전달 된 default value를 사용해야 합니다.


@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
    if (restorePersistedValue) {
        // Restore existing state
        mCurrentValue = this.getPersistedInt(DEFAULT_VALUE);
    } else {
        // Set default state from the XML attribute
        mCurrentValue = (Integer) defaultValue;
        persistInt(mCurrentValue);
    }
}


각각의 getPersisted*() 메소드는 persist 값이 없거나 key 가 존재하지 않을 경우 사용될 default 값을 받게 됩니다. 위 예제에서 local constant는 getPersistedInt()가 persisted 값을 return 하지 못학 경우 사용될 default 값을 지정해 놓았습니다.

Caution : getPersisted*() 메소드에서 defaultValue를 default 값으로 사용할 수 없습니다. 왜냐하면 restorePersistedValue 가 true 이면 이 갑은 항상 null 일 것이기 때문입니다.



Providing a default value


당신의 Preference class 의 instance 가 default value를 가리키고 있다면 (with the android:defaultValue attribute) 시스템은 값을 가져오기 위해 해당 object를 instantiate할 때 onGetDefaultValue() 를 호출할 것입니다.  이 메스드를 반드시 implement 해서 시스템이 이 default value를 SharedPreferences 파일에 저장할 수 있도록 해야 합니다.


@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getInteger(index, DEFAULT_VALUE);
}


이 method argument들은 여러분이 필요로 하는 모든 것들을 제공 합니다. : the array of attributes and the index position of the android:defaultValue, 여러분이 반드시 retrieve 해야 할 것들이죠. 이 메소드를 attribute에서 디폴트 값을 추출하기 위해 반드시 implement 해야 하는 이유는 해당 값이 undefined 됐을 경우 attribute에 대해 local default 값을 specify 해야만 하기 때문입니다.



Saving and restoring the Preference's state


layout 의 View 처럼, 여러분의 Preference subclass는 액티비티나 fragment 가 restart 되는 경우 그 상태를 save 하거나 restoring 해야 하기 때문입니다. (사용자가 화면을 rotate 시켰을 경우). Preference class의 상태를 정확하게 저장하거나 restore 하기 위해서는 lifecycle callback 메소드인 onSaveInstanceState() and onRestoreInstanceState()를 호출 해야만 합니다.

Preference의 state는 Parcelable interface 를 implement 한 object 에 의해 정의 됩니다. 안드로이드 프레임워크는 여러분의 state object를 정의하기 위한 starting point로서 이런 object를 제공합니다.

어떻게 이 Preference class 가 그 상태를 저장하는지 정의하기 위해 Preference.BaseSavedState class 를 extend 해야 합니다. 그리고 몇개의 메소드들을 override 하고 CREATOR 객체를 정의합니다.

대부분의 앱들은, 아래와 같이 implementation을 사용할 수 있습니다. Integer 가 아닌 경우 아래 에제에서 몇 줄만 바꿔서 사용하시면 됩니다.



private static class SavedState extends BaseSavedState {
    // Member that holds the setting's value
    // Change this data type to match the type saved by your Preference
    int value;

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public SavedState(Parcel source) {
        super(source);
        // Get the current preference's value
        value = source.readInt();  // Change this to read the appropriate data type
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        // Write the preference's value
        dest.writeInt(value);  // Change this to write the appropriate data type
    }

    // Standard creator object using an instance of this class
    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {

        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

Preference.BaseSavedState implementation을 여러분 앱에 추가 했다면 (usually as a subclass of your Preference subclass), 여러분은 Preference subclass에 대해 onSaveInstanceState() and onRestoreInstanceState()를 implement 합니다.


For example:


@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    // Check whether this Preference is persistent (continually saved)
    if (isPersistent()) {
        // No need to save instance state since it's persistent,
        // use superclass state
        return superState;
    }

    // Create instance of custom BaseSavedState
    final SavedState myState = new SavedState(superState);
    // Set the state's value with the class member that holds current
    // setting value
    myState.value = mNewValue;
    return myState;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    // Check whether we saved the state in onSaveInstanceState
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save the state, so call superclass
        super.onRestoreInstanceState(state);
        return;
    }

    // Cast state to custom BaseSavedState and pass to superclass
    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());

    // Set this Preference's widget to reflect the restored state
    mNumberPicker.setValue(myState.value);
}

반응형

[Android] Settings - 2 -

2016. 10. 10. 09:07 | Posted by 솔웅


반응형

Settings 2


Using Preference Headers

드문 경우 이지만 세팅 화면을 제일 처음에 보여야 할 때도 있을 것입니다. (예 figures 4 and 5). 안드로이드 3.0 이상에서는 headers 기능을 사용해야 합니다. (nested preferenceScreen element로 subscreen들을 구현하는 대신에...)

To build your settings with headers, you need to:
headers를 이용해 세팅을 만들려면

    세팅의 각 그룹을 구분해 PreferenceFragment의 별도의 instance들로 만듭니다. 즉 각 그룹들은 별도의 XML 파일로 나눠져야 한다는 의미입니다.
    각 세팅 그룹별로 XML headers 파일을 생성합니다. 그리고 해당 세팅을 갖고 있는 fragment를 정의합니다.
    setting을 host 하기 위해 PreferenceActivity class 를 extend 합니다.
    headers file을 지정하기 위해 onBuildHeaders() callback을 implement 합니다.

이 디자인을 사용하는 가장 큰 잇점은 큰 화면에서 작동할 시 PreferenceActivity 가 2개의 pane layout으로 자동적으로 표현한다는 겁니다. (figure 4)

안드로이드 3.0 이전의 버전에서도 이 PreferenceFragment 를 사용해 2개의 pane을 표시할 수 있습니다. (see the section about Supporting older versions with preference headers)



Figure 4. Two-pane layout with headers.



1. The headers are defined with an XML headers file.
2. Each group of settings is defined by a PreferenceFragment that's specified by a <header> element in the headers file.





Figure 5. A handset device with setting headers. When an item is selected, the associated PreferenceFragment replaces the headers.



Creating the headers file



headers list에 있는 세팅 그룹은 root <preference-headers> element 안에 하나의 <header> element 로 정의 됩니다.



예제.

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <!-- key/value pairs can be included as arguments for the fragment. -->
        <extra android:name="someKey" android:value="someHeaderValue" />
    </header>
</preference-headers>


android:fragment attribute와 함께 각 header 는 PreferenceFragment 의 instance 를 정의 합니다. 사용자가 header를 선택했을 때 open 되는 대상이죠.



<extras> element는 key-value 값을 fragment로 pass 할 수 있도록 해 줍니다. ( in a Bundle). 이 fragment는 getArguments()를 호출함으로서 arguments들을 수집할 수 있습니다. argument들을 fragment로 pass 해야 하는 이유는 여러가지가 있을 수 있습니다. 그 중 하나는 PreferenceFragment 의 같은 subclass를 각 그룹에 대해 reuse 할 수 있다는 겁니다. 그리고 이 argument를 어떤 fragment의 preference XML 파일을 load 해야 하는지 특정할 수 있습니다.



예를 들어, 아래를 보면 여러개의 settings group들에서 reuse 될 수 있는 fragment가 있습니다. 각 header가 <extra> argument를 "settings" key와 함께 정의하고 있습니다. :



public static class SettingsFragment extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String settings = getArguments().getString("settings");
        if ("notifications".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_wifi);
        } else if ("sync".equals(settings)) {
            addPreferencesFromResource(R.xml.settings_sync);
        }
    }
}





Displaying the headers


preference headers를 표시하려면 onBuildHeaders() callback 메소드를 implement 해야 합니다. 그리고 loadHeaderFromResource() 를 호출합니다.


예제


public class SettingsActivity extends PreferenceActivity {
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }
}



headers 의 리스트에서 사용자가 한 아이템을 선택했을 때 시스템은 해당 PreferenceFragment를 open 합니다.


Note : preference headers를 사용 할 때 PreferenceActivity의 subclass는 onCreate() 메소드를 implement 할 필요가 없습니다. 이 activity 에서 필요한 작업은 headers를 load 하는 것이기 때문입니다.



Supporting older versions with preference headers



앱이 Android 3.0 이전의 버전을 지원 할 때에도 headers를 사용해 Android 3.0 이사의 버전에서 작동할 때 2개의 pane layout을 제공할 수 있습니다. 이렇게 하려면 추가적인 preferences XML을 만들어야 하는데요 basic <Preference> element를 사용해서 header items 처럼 사용되도록 하면 됩니다. (오래된 안드로이드 버전에서 사용되도록 하기 위해)

새로운 PreferenceScreen을 여는 대신 각 <Preference> element가 PreferenceActivity에 Intent를 보냅니다. (어떤 XML file이 로드되는지를 지정합니다.)



예제) preference headers 를 위한 XML 파일입니다. 안드로이드 3.0 이상에서 사용됩니다. (res/xml/preference_headers.xml):



<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header
        android:fragment="com.example.prefs.SettingsFragmentOne"
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one" />
    <header
        android:fragment="com.example.prefs.SettingsFragmentTwo"
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" />
</preference-headers>



안드로이드 3.0 이하 버전들에 같은 headers를 제공하기 위한 preference file 입니다.(res/xml/preference_headers_legacy.xml):


<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <Preference
        android:title="@string/prefs_category_one"
        android:summary="@string/prefs_summ_category_one"  >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_ONE" />
    </Preference>
    <Preference
        android:title="@string/prefs_category_two"
        android:summary="@string/prefs_summ_category_two" >
        <intent
            android:targetPackage="com.example.prefs"
            android:targetClass="com.example.prefs.SettingsActivity"
            android:action="com.example.prefs.PREFS_TWO" />
    </Preference>
</PreferenceScreen>


<preference-headers> 를 지원하는 것은 안드로이드 3.0에서 추가 되었기 때문에 시스템은 안드로이드 3.0 이상에서 작동 될 때에만 PreferenceActivity 안의 onBuildHeaders()를 호출합니다. legacy headers file을 로딩하려면 (preference_headers_legacy.xml) 우선 안드로이드 버전을 체크 합니다. 안드로이드 3.0 (HONEYCOMB)보다 오래 됐으면 addPreferencesFromResource() 를 호출해서 legacy header file을 로딩합니다.



예제


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

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}

// Called only on Honeycomb and later
@Override
public void onBuildHeaders(List<Header> target) {
   loadHeadersFromResource(R.xml.preference_headers, target);
}



이제 남은 것은 어떤 preference file을 load 해야 하는지를 지정하는 activity로 pass 된 Intent를 처리하는 것입니다. 그 intent의 action을 retrieve 하고 preference XML의 <intent> 태그에서 사용돼 알고 있는 action string과 비교 합니다.

final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE";
...

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

    String action = getIntent().getAction();
    if (action != null && action.equals(ACTION_PREFS_ONE)) {
        addPreferencesFromResource(R.xml.preferences);
    }
    ...

    else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
        // Load the legacy preferences headers
        addPreferencesFromResource(R.xml.preference_headers_legacy);
    }
}


addPreferencesFromResource()에 대한 연속적인 호출은 single list안의 모든 preference들이 stack 되게 할 겁니다. 그러니 if-else 구문을 사용해서 오직 한번만 호출되도록 하세요.





Reading Preferences



디폴트로 preference들은 여러분 앱 내부 어디에서든지 접근 가능한 파일에 저장됩니다. 접근은 PreferenceManager.getDefaultSharedPreferences() 라는 static method를 호출함으로서 이뤄집니다. 이 메소드는 SharedPreferences 객체를 return 하는데요 여기에는 PreferenceActivity에서 사용된 Preference 객체들과에 상응하는 key-value 값들이 포함돼 있습니다.



예제

SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");



Listening for preference changes

사용자가 preference를 변경하자 마자 통보를 받아야 할 때가 있을 수 있습니다. 이렇게 변화가 있었을 때 callback을 받으려면 SharedPreference.OnSharedPreferenceChangeListener 인터페이스를 implement 합니다. 그리고 그 리스너를 registerOnSharedPreferenceChangeListener() 를 호출함으로서 SharedPreferences 객체에 등록해 놓습니다.

이 인터페이스는 한개의 callback 메소드를 가지고 있습니다. onSharedPreferenceChanged() 메소드 입니다.



예제



public class SettingsActivity extends PreferenceActivity
                              implements OnSharedPreferenceChangeListener {
    public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType";
    ...

    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
        String key) {
        if (key.equals(KEY_PREF_SYNC_CONN)) {
            Preference connectionPref = findPreference(key);
            // Set summary to be the user-description for the selected value
            connectionPref.setSummary(sharedPreferences.getString(key, ""));
        }
    }
}


이 예제에서는 이미 알고 있는 preference key 에 대해 체크하고 있습니다. findePreference() 메소드를 호출해서 Preference 객체를 get 합니다. 이 preference는 변경된 preference 입니다. 그래서 그 아이템의 해당 summary로 내용을 업데이트 할 수 있습니다. List나 다른 multiple choice setting 인 경우에는 현재의 상태를 표시하기 위해 setSummary()를 호출해야 할 것입니다. such as the Sleep setting shown in figure 5)

Note : Android Design document에 Settings에 대해 나와 있듯이 사용자가 preference를 변경할 때마다 ListPreference의 Summary를 업데이트 해 줄 것을 권장합니다

Activit의 제대로 된 lifecycle management를 위해 SharedPreferences.OnSharedPreferenceChangeListeneronResume(), onPause() callback에서 사용할 것을 권장합니다.



@Override
protected void onResume() {
    super.onResume();
    getPreferenceScreen().getSharedPreferences()
            .registerOnSharedPreferenceChangeListener(this);
}

@Override
protected void onPause() {
    super.onPause();
    getPreferenceScreen().getSharedPreferences()
            .unregisterOnSharedPreferenceChangeListener(this);
}



주의 : registerOnSharedPreferenceChangeListener()를 호출할 때 preference manager는 strong reference 를 listener에 저장하지 않습니다. 여러분이 직접 저장해야 합니다. 그렇지 않으면 garbage collection으로 가게 될 것입니다. 그 reference를 객체의 instance data 안의 리스너에 보관할 것을 권장합니다. 그러면 리스너를 필요로 하는 한 존재하게 할 수 있습니다.



아래 예제에서는 caller가 리스너에 reference를 keep 하지 않습니다. 그래서 이것은 garbage collection으로 가게 될 겁니다. 그러면 언젠가 앱의 독장이 fail 될 겁니다.


prefs.registerOnSharedPreferenceChangeListener(
  // Bad! The listener is subject to garbage collection!
  new SharedPreferences.OnSharedPreferenceChangeListener() {
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    // listener implementation
  }
});

대신에 객체의 인스턴스 데이터 필드안에 있는 리스너에 reference를 저장하면 해당 리스너가 필요할 때 까지 계속 존재하게 될 겁니다.

SharedPreferences.OnSharedPreferenceChangeListener listener =
    new SharedPreferences.OnSharedPreferenceChangeListener() {
  public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    // listener implementation
  }
};
prefs.registerOnSharedPreferenceChangeListener(listener);


Managing Network Usage

안드로이드 4.0 부터 Settings application은 사용자에게 앱들이 얼마나 많은 네트워크 데이터를 사용했는지를 보여줍니다. 사용자는 이것을 보고 각각의 앱에 대해 background data 사용을 금지하는 등의 조치를 할 수 있습니다. 그러니까 사용자가 여러분의 앱의 데이터 access를 잠그지 않도록 하기 위해서는 데이터를 가급적 적게 효율적으로 사용하도록 개발 하여야 합니다.

예를 들어 얼마나 자주 데이터를 동기화 하는지 혹은 Wi-Fi 하에서만 파일 업로드 다운로드를 가능하도록 하던지 혹은 로밍시에만 가능하도록 하는 등등의 기능을 넣어서 데이터 사용을 줄일 수 있습니다.


Once you've added the necessary preferences in your PreferenceActivity to control your app's data habits, you should add an intent filter for

PreferenceActivity에 필요한 preference들을 추가 했다면 다음은 intent filter를 추가해야 합니다.

manifest파일의 ACTION_MANAGE_NETWORK_USAGE 에 추가 합니다.


<activity android:name="SettingsActivity" ... >
    <intent-filter>
       <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
       <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

이 intent filter는 이 activity는 어플리케이션의 데이터를 컨트롤 한다는 것을 알려 줍니다.

반응형


반응형

Lesson 3의 나머지 부분에서는 Intent에 대한 설명이 나옵니다.
Intent 는 다른 Activity를 실행시키는 명령어로 같은 앱 내의 Activity 이외에 다른 앱의 액티비티를 실행 시킬 수 있습니다.
그러니까 내 앱을 사용하다가 전화거는 서비스를 제공해야 하면 Intent를 사용해서 다른 전화거는 앱 즉 안드로이드 자체에 실려있는 전화거는 앱을 이 Intent를 사용해서 실행하면 됩니다
문자메세지, 바코드 스캐닝, 카메라 등등의 기능도 그냥 다른 앱을 불러서 사용하도록 하면 됩니다.

이 때 Intent를 사용하면 Activity의 정확한 이름 즉 클래스 이름을 제공하는데 다른 앱의 액티비티 클래스 이름을 알 수는 없습니다.
이럴 때 Implicit Intents를 사용할 수 있습니다.

안드로이드는 이 Implicit Intents에서 사용한 이름의 액티비티가 여러개 있을 때 그 중 하나를 선택할 수 있는 메뉴를 보여 줍니다.

실습을 해 보겠습니다.

지금까지 만든 일기예보 앱에 Map을 보여주는 기능을 추가 할 겁니다.
지도는 구글맵을 불러와서 해당 지역을 그 구글 맵에 표시하도록 할 겁니다.

우선 menu option을 추가해서 지도에 사용자가 정한 지역을 표시하도록 할 겁니다.


res-menu-main.xml 파일에 위와 같이 지도를 위한 메뉴 아이템을 추가 합니다.



그리고 MainActivity에 아래와 같이 openPreferredLocationInMap()을 추가 합니다.

    private void openPreferredLocationInMap() {
        SharedPreferences sharedPrefs =
                PreferenceManager.getDefaultSharedPreferences(this);
        String location = sharedPrefs.getString(
                getString(R.string.pref_location_key),
                getString(R.string.pref_location_default));

        // Using the URI scheme for showing a location found on a map.  This super-handy
        // intent can is detailed in the "Common Intents" page of Android's developer site:
        // http://developer.android.com/guide/components/intents-common.html#Maps
        Uri geoLocation = Uri.parse("geo:0,0?").buildUpon()
                .appendQueryParameter("q", location)
                .build();

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(geoLocation);

        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivity(intent);
        } else {
            Log.d(LOG_TAG, "Couldn't call " + location + ", no receiving apps installed!");
        }
    }


내용을 보면 SharedPreference를 불러오고 location 값을 get합니다.

그리고 URI에 이 location값을 넣어 줍니다.
우편번호로 지도상의 위도와 경도를 가져와서 URI에 넣어 줍니다.
이 부분을 자세히 보시려면 이곳에 가시면 됩니다.
http://developer.android.com/guide/components/intents-common.html#Maps

그리고 Intent에서 Intent.ACTION_VIEW 라는 Implicit Intent를 사용합니다.

그리고 이 intent에 Uri (위도와 경도가 있는)를 setting 합니다.
그러면 이 위도와 경도 정보를 Intent.ACTION_VIEW라는 액티비티로 넘겨 줄 준비가 돼어 있는 겁니다.

그리고 if 문에서 resolveActivity를 사용해서 이 Implicit Intent에 해당하는 액티비티가 있는 지 확인합니다.
있으면 startActivity를 하고 없으면 로그를 뿌려 줍니다.


그리고 나서  onOptionsItemSelected()에 아래와 같이 map 관련 부분을 추가 합니다.
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            startActivity(new Intent(this, SettingsActivity.class));
            return true;
        }

        if (id == R.id.action_map) {
            openPreferredLocationInMap();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

이제 Setting에서 Map Location을 누르면 해당 지역의 위도 경도가 구글맵 애플리케이션에 전달되서 구글맵 앱에 해당 지역 위치가 보일 겁니다.



참고로 Share Intent 에 대해 자세히 보려면 이곳으로 가면 됩니다.
https://developer.android.com/training/sharing/shareaction.html

Lesson 3의 다음 부분을 보면 Detail 화면에서 날씨를 Share 하거나 문자 메세지로 보내는 기능이 추가 됩니다.

이 때 Intent Share의 Share Provider를 사용합니다.


이 기능을 구현하는 순서는 우선 res-menu 에 detailfragment.xml을 아래와 같이 만듭니다.
여기서 ShareActionProvider를 사용했습니다.

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/action_share"
        android:title="@string/action_share"
        app:showAsAction="always"
        app:actionProviderClass="android.support.v7.widget.ShareActionProvider" />
</menu>

그 다음 DetailActivity 클래스로 갑니다.

그리고 나서 DetailFragment 클래스를 아래와 같이 업데이트 합니다.

   /**
     * A placeholder fragment containing a simple view.
     */
    public static class DetailFragment extends Fragment {

        private static final String LOG_TAG = DetailFragment.class.getSimpleName();

        private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
        private String mForecastStr;

        public DetailFragment() {
            setHasOptionsMenu(true);
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {

            View rootView = inflater.inflate(R.layout.fragment_detail, container, false);

            // The detail Activity called via intent.  Inspect the intent for forecast data.
            Intent intent = getActivity().getIntent();
            if (intent != null && intent.hasExtra(Intent.EXTRA_TEXT)) {
                mForecastStr = intent.getStringExtra(Intent.EXTRA_TEXT);
                ((TextView) rootView.findViewById(R.id.detail_text))
                        .setText(mForecastStr);
            }

            return rootView;
        }

        @Override
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            // Inflate the menu; this adds items to the action bar if it is present.
            inflater.inflate(R.menu.detailfragment, menu);

            // Retrieve the share menu item
            MenuItem menuItem = menu.findItem(R.id.action_share);

            // Get the provider and hold onto it to set/change the share intent.
            ShareActionProvider mShareActionProvider =
                    (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);

            // Attach an intent to this ShareActionProvider.  You can update this at any time,
            // like when the user selects a new piece of data they might like to share.
            if (mShareActionProvider != null ) {
                mShareActionProvider.setShareIntent(createShareForecastIntent());
            } else {
                Log.d(LOG_TAG, "Share Action Provider is null?");
            }
        }

        private Intent createShareForecastIntent() {
            Intent shareIntent = new Intent(Intent.ACTION_SEND);
            shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
            shareIntent.setType("text/plain");
            shareIntent.putExtra(Intent.EXTRA_TEXT,
                    mForecastStr + FORECAST_SHARE_HASHTAG);
            return shareIntent;
        }
    }
   
일단 onCreateView 에서 mForecastStr 라는 member variable 을 사용해서 text를 세팅했습니다.

그리고 createShareForecastIntent()를 만듭니다.
여기서는 문자메세지를 보내기 위해 필요한 세팅들을 합니다.
putExtra를 사용해서 내용을 추가하는데 그 내용은 해당 detail 페이지에 있는 날씨 정보 입니다.
그리고 이 shareIntent를 return 합니다.

이 클래스는 onCreateOptionsMenu에서 해당 아이콘이 클릭되면 호출 됩니다.

이제 Detail 화면에서 메세지 아이콘을 누르면 아래와 같이 문자메세지 앱이 실행됩니다.



그 이외에 Broadcast Intent 가 있습니다.

예를 들어 핸드폰이 충전되고 있는지를 Broadcast해 주는데요.  이 Broadcast되는 정보를 받아서 앱에서 활용할 수 있습니다.

ACTION_POWER_CONEECTED 이벤트를 사용하고 리시버는 Manifest Receiver를 사용해서 구현합니다.

자세한 내용은 이곳을 참고 하세요.

https://developer.android.com/reference/android/content/BroadcastReceiver.html



반응형


반응형

이번엔 Settings 를 꾸밀 겁니다.


Setting 관련 한 Android Developer API Guide는 여기에 있습니다.

SharedPreference API


이 Sunshine  앱에서는 Setting UX 가 아래와 같이 진행 될 겁니다.



우편번호를 입력하는 화면과 화씨/섭씨 를 선택할 수 있는 기능을 제공할 겁니다.


먼저 SettingsActivity를 생성합니다.


안드로이드 버전에 따라 PreferenceActivity와 PreferenceFragment를 사용합니다.



이렇게 SettingsActivity.java  를 생성합니다.


public class SettingsActivity extends PreferenceActivity
        implements Preference.OnPreferenceChangeListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Add 'general' preferences, defined in the XML file
        addPreferencesFromResource(R.xml.pref_general);

        // For all preferences, attach an OnPreferenceChangeListener so the UI summary can be
        // updated when the preference changes.
        bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_location_key)));
        bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_units_key)));
    }

    /**
     * Attaches a listener so the summary is always updated with the preference value.
     * Also fires the listener once, to initialize the summary (so it shows up before the value
     * is changed.)
     */
    private void bindPreferenceSummaryToValue(Preference preference) {
        // Set the listener to watch for value changes.
        preference.setOnPreferenceChangeListener(this);

        // Trigger the listener immediately with the preference's
        // current value.
        onPreferenceChange(preference,
                PreferenceManager
                        .getDefaultSharedPreferences(preference.getContext())
                        .getString(preference.getKey(), ""));
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object value) {
        String stringValue = value.toString();

        if (preference instanceof ListPreference) {
            // For list preferences, look up the correct display value in
            // the preference's 'entries' list (since they have separate labels/values).
            ListPreference listPreference = (ListPreference) preference;
            int prefIndex = listPreference.findIndexOfValue(stringValue);
            if (prefIndex >= 0) {
                preference.setSummary(listPreference.getEntries()[prefIndex]);
            }
        } else {
            // For other preferences, set the summary to the value's simple string representation.
            preference.setSummary(stringValue);
        }
        return true;
    }

}


PreferenceActivity를 extends 하고 Preference.OnPreferenceChangeListener 를 implements 합니다.

처음 onCreate() 메소드에서는 해당 preference xml을 불러옵니다.
그리고 나서 Listener를 구현해서 해당 Preference에 어떤 변화가 있을 때 UI Summary를 update 할 수 있도록 합니다.

그 구체적인 내용은 bindPreferenceSummaryToValue()에 구현해 놓습니다.

그리고 Override한 onPreferenceChange()메소드가 있습니다.

여기에서는 ListPreference일 경우 해당하는 아이템을 고르도록 하고 그렇지 않은 경우는 그냥 Summary를 가져 오도록 합니다.

이렇게 되면 사용자가 우편번호를 바꾸거나 섭씨/화씨 조건을 바꿨을 때 그에 해당하는 값을 가져올 수 있게 됩니다.


이렇게 SettingsActivity  클래스를 만들었으면 이를 AndroidManifest에 등록해야 합니다.


        <activity
            android:name=".SettingsActivity"
            android:label="@string/title_activity_settings"
            android:parentActivityName=".MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.android.sunshine.app.MainActivity" />
        </activity>


label은  string xml에 지정해 놓은 값을 사용합니다.

그리고 parentActivityName은 MainActivity로 합니다.

이렇게 되면 Back  버튼을 누르게 되면 MainActivity로 가게 됩니다.


이제 MainActivity에서도 약간 손을 좀 봐야 하는데요.


onOptionsItemSelected()를 아래와 같이 코딩합니다.


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            startActivity(new Intent(this, SettingsActivity.class));
            return true;
        }

        return super.onOptionsItemSelected(item);
    }


Option item이 선택 됐을 경우 이것이 action_settings일 경우 SettingsActivity를 실행하도록 Intent를 사용합니다.


여기까지 하면 MainActivity에서 Setting를 선택하면 SettingsActivity  화면으로 넘어가게 됩니다.

아직 빈화면만 보일 겁니다.


DetailActivity에서도 이 onOptionsItemSelected()를 똑 같이 구현해 놓으면 이곳에서도 Settings를 선택하면 SettingsActivity화면으로 넘어가도록 할 수 있습니다.


아직 SettingsActivity는 빈화면이니까 이 화면을 구현할 차례입니다.


일단 화면의 Layout을 잡아줍니다.


그러려면 res에 xml폴더를 생성해서 아래와 같이 pref_general.xml을 만듭니다.



한개의 EditTextPreference와 한개의 ListPreference를 만들었습니다.


참고로 strings.xml에는 아래와 같은 내용들이 있습니다.


  <!-- Label for the location preference [CHAR LIMIT=30] -->
    <string name="pref_location_label">Location</string>

    <!-- Key name for storing location in SharedPreferences [CHAR LIMIT=NONE] -->
    <string name="pref_location_key" translatable="false">location</string>

    <!-- Default postal code for location preference [CHAR LIMIT=NONE] -->
    <string name="pref_location_default" translatable="false">55347</string>

    <!-- Label for the temperature units preference [CHAR LIMIT=30] -->
    <string name="pref_units_label">Temperature Units</string>

    <!-- Label for metric option in temperature unit preference [CHAR LIMIT=25] -->
    <string name="pref_units_label_metric">Imperial</string>

    <!-- Label for imperial option in temperature unit preference [CHAR LIMIT=25] -->
    <string name="pref_units_label_imperial">Imperial</string>

    <!-- Key name for temperature unit preference in SharedPreferences [CHAR LIMIT=NONE] -->
    <string name="pref_units_key" translatable="false">units</string>

    <!-- Value in SharedPreferences for metric temperature unit option [CHAR LIMIT=NONE] -->
    <string name="pref_units_metric" translatable="false">metric</string>

    <!-- Value in SharedPreferences for imperial temperature unit option [CHAR LIMIT=NONE] -->
    <string name="pref_units_imperial" translatable="false">imperial</string>



SettingsActivity에 보시면 addPreferencesFromResource(R.xml.pref_general); 이있습니다.

방금 만든 pref_general을 불러오는 부분이죠.


그리고 나서 아래와 같이 리스너를 물려 줍니다.

        bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_location_key)));
        bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_units_key)));


이미 위 SettingsActivity에서 다 구현해 놨던 겁니다.



여기서 사용자가 우편번호를 입력하면 그 값은 자동으로 SharedPreference에 저장이 됩니다.

그래서 날씨를 표시할 때 사용자가 입력한 우편번호를 SharedPreference에서 가져와서 그 지역 날씨를 표시하게 되죠.

만약 사용자가 입력한 우편번호가 없다면 저 strings.xml에 있는 default값인 55347을 사용하게 됩니다.


이것은 ForecastFragment에 있는 onOptionsItemSelected() 를 아래와 같이 코딩하면 구현할 수 있습니다.


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_refresh) {
            updateWeather();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


옵현 아이템이 선택되면 그 ID 가 refresh 이면 updateWeather()를 돌리도록 합니다.


    private void updateWeather() {
        FetchWeatherTask weatherTask = new FetchWeatherTask();
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
        String location = prefs.getString(getString(R.string.pref_location_key),
                getString(R.string.pref_location_default));
        weatherTask.execute(location);
    }


updateWeather에서는 location 값을 받아서 weatherTask.execute()에 넘겨 줍니다.


이 weatherTask는 이전에 구현해 놓은 FetchWeatherTask로 웹사이트에 날씨를 요청하고 받아서 이를 뿌려주도록 합니다.


참고로 ForecaseFragment 클래스의 onStart()를 아래와 같이 구현합니다.


    @Override
    public void onStart() {
        super.onStart();
        updateWeather();
    }


그러면 앱이 처음 실행할 때도 디폴트 값을 가져와서 그 지역의 날씨를 표시하게 됩니다.


이제 섭씨/화씨를 선택하는 부분을 볼건데요.


위에 소스코드에도 있듯이 여기서는 ListPreference를 사용할 겁니다.


참고로 imperial은 화씨를 말하고 metric은 섭씨를 말합니다.


T



emperature Units  부분은 stings.xml을 변경하는 과정에서 실수가 있어서 Imperial만 뜨네요.

다시 strings.xml을 수정해야 겠습니다.


일단 Settings 와 관련 된 부분은 모두 마쳤습니다.


여기까지의 소스코드는 아래에서 받을 수 있습니다.

https://github.com/udacity/Sunshine-Version-2/tree/3.11_add_units_setting




반응형
이전 1 다음