개인(Personal)/안드로이드 (Andorid Studio)

[안드로이드 스튜디오] Ch15. 데이터 이동 & 저장 (putExtra & sharedPreferences)

LePenseur 2019. 9. 4. 09:44
반응형

2019년 9월04일 (수)




*질적으로 많이 부족한 글입니다. 지적 및 조언 환영합니다.
*이 글에선 "안드로이드 스튜디오"를 줄여서 "안스" 라고 표현합니다. 참고 바랍니다.

우선, 본인은 학원에서 'JAVA' 기초 수업을 선수강했다. 안드로이드 스튜디오를 배우기 위해선 피할 수 없는 언어이기 때문이다.


잡담: 데이터 이동 및 저장쪽으로 들어가서 굉장히 헤맬줄알았지만, 코드에 어느정도 익숙해진걸까? 수업내용을 따라가는것이 어느정도 수월해지고 퀴즈를 푸는 나의 자세도 매우 능동적으로 변했다. 하지만 곧 안스 수업이 종강하면 나홀로서기를 해야할텐데... 걱정이다.



이번글은 총 두가지의 큰 맥락을 중점으로한다. 바로 '데이터 이동' 과 '데이터 저장' 이다.
**여기서 잠시 헷갈려하면 안되는데 이전에 배운 intent는 화면, 즉 엑티비티의 이동만일뿐, 데이터도 함께 이동하는것은 아니었다!


<차례>
1. MyDataMove - 데이터 이동
2. MySharedPreferences (정답 코드와 비교) - 데이터 저장


1. MyDataMove
1.1 결과
사진1 - 초기 화면
사진2 - 생년월일 캘린더
사진3 - 모든 정보 입력시 화면
사진4 - "SEND"버튼 클릭시 전환 화면


설명: 위 사진 그대로다. 두개의 레이아웃과 그에 따르는 두개의 자바파일이 존재하면 되는거다. 단, 여기서의 키포인트는 당연히 저장된 데이터가 서브 엑티비티로 어떻게 넘오느냐다. 이점에 유의해서 밑의 코드를 살펴보자.

1.2 코드
1.21 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:id="@+id/edit_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="이름 입력"
            android:textSize="30dp"/>

        <EditText
            android:id="@+id/edit_age"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="나이 입력"
            android:textSize="30dp"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/edit_b_day"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:textSize="30dp"/>
            <Button
                android:id="@+id/date"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Birth Day"
                android:textSize="20dp"/>

        </LinearLayout>

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send"
            android:layout_gravity="right"
            android:layout_marginEnd="15dp"
            android:textSize="30dp"/>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>


설명: 초기 화면 디자인. 버튼말고도 나머지것들에 아이디를 추가한다. 그 이유는 밑의 MainActivity에서 알아보자.

1.22 activity_sub.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="30dp"/>


        <TextView
            android:id="@+id/text2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="30dp"/>


        <TextView
            android:id="@+id/text3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="30dp"/>

    </LinearLayout>

</LinearLayout>


설명: 받아온 정보들을 출력하는 서브 레이아웃. 받아올 정보가 3가지이기에 당연히 텍스트뷰도 3개다.

1.23 MainActivity.java
package com.example.mydatatmove;

import androidx.appcompat.app.AppCompatActivity;

import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TextView;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private Button send, date;
    private Dialog dialog;
    private EditText edit_b_day, edit_name, edit_age;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //객체화
        edit_name = (EditText)findViewById(R.id.edit_name);
        edit_age = (EditText)findViewById(R.id.edit_age);
        edit_b_day = (EditText)findViewById(R.id.edit_b_day);

        date = (Button)findViewById(R.id.date);

        date.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //현재날짜 먼저 구해주어야 한다. -> Calendar 클래스를 이용하자
                Calendar now = Calendar.getInstance();

                int y = now.get(Calendar.YEAR);
                int m = now.get(Calendar.MONTH);
                int d = now.get(Calendar.DAY_OF_MONTH);

                //그다음으로는 다이얼로그 이용
                //날짜를 처리하는 리스너가 있다 -> dateSetListener
                dialog = new DatePickerDialog(MainActivity.this,dateSetListener,y,m,d);

                dialog.show();


            }

        });

        send = (Button)findViewById(R.id.send);
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //화면전환 (intent 객체 생성)
                Intent i = new Intent(MainActivity.this,SubActivity.class);

                //입력된 데이터 받기
                String name = edit_name.getText().toString();
                String age = edit_age.getText().toString();
                String b_day = edit_b_day.getText().toString();

                // 데이터를 다른 Activity로 넘기는 방법 2가지:

                // (방식1) Intent 를 통하여 전달하는 방식

                i.putExtra("name",name);
                i.putExtra("age",age);
                i.putExtra("b_day",b_day);


                // (방식2) Bundle 를 통하여 전달하는 방식 (현업에서는 bundle방식 선호, 하지만 큰차이는 없음.)

                Bundle bundle = new Bundle();
                bundle.putString("name",name);
                bundle.putString("age",age);
                bundle.putString("b_day",b_day);

                i.putExtras(bundle); // intent 객체에 Bundle 을 저장

                startActivity(i);

            }
        });

    }

    DatePickerDialog.OnDateSetListener dateSetListener = new DatePickerDialog.OnDateSetListener() {
        @Override
        // i=년, i1=월, i2=일
        public void onDateSet(DatePicker datePicker, int i, int i1, int i2) {
            String starDate = String.format("%d-%02d-%02d",i,i1+1,i2);

            edit_b_day.setText((starDate));
            //질문: 왜 i1에 1을 더하는가?
            //대답: 왜냐하면 1월이 0이고 2월 1이기 때문이다.
            //정리: 월은 monthOfYear 이고 0부터 차례대로 배열되어 있다.
        }
    };
}


설명: 물론 캘린더 생성 코드도 중요하지만 이번 프로젝트에서 가장 집중해야할 부분은 바로 'save 버튼 기능화' 밑에 존재하는 '데이터 저장 코드' 이다.

        send = (Button)findViewById(R.id.send);
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //화면전환 (intent 객체 생성)
                Intent i = new Intent(MainActivity.this,SubActivity.class);

                //입력된 데이터 받기
                String name = edit_name.getText().toString();
                String age = edit_age.getText().toString();
                String b_day = edit_b_day.getText().toString();

                // 데이터를 다른 Activity로 넘기는 방법 2가지:

                // (방식1) Intent 를 통하여 전달하는 방식

                i.putExtra("name",name);
                i.putExtra("age",age);
                i.putExtra("b_day",b_day);


                // (방식2) Bundle 를 통하여 전달하는 방식 (현업에서는 bundle방식 선호, 하지만 큰차이는 없음.)

                Bundle bundle = new Bundle();
                bundle.putString("name",name);
                bundle.putString("age",age);
                bundle.putString("b_day",b_day);

                i.putExtras(bundle); // intent 객체에 Bundle 을 저장

                startActivity(i);

            }
        });


설명: 중요하기에 그 부분만 따로 잘라내어왔다. 순서대로 정리해보자면...
(1) intent로 하여금 서브 엑티비티로 넘어가는 기능을 생성
(2) 저장을 위해 매개변수 생성, 그안에 들어갈 데이터 설정
(예시: String name = edit.name.getText().toString(); -> 기본변수 name안에 edit.name에서 받는 모든 텍스트를 받겠다)
(3)데이터 저장 두가지의 방식 (둘중 하나만 쓰면 된다):

  • Intent:
    • intent 재정의 -> i.putExtra(키값, 벨류값) 
    • 어차피 화면전환을 위해 intent는 재정의 되어있음 (다시 할 필요 없음)
    • intent로 이미 만들었기에 따로 또 저장할 필요 없음.
  • Bundle:
    • Bundle 재정의 -> bundle.putString(키값,벨류값)
    • 기존의 intent에 bundle이라는 객체를 추가했기에 재정의 해야함.
    • 화면전환을 위해 존재하는 intent에 Bundle을 저장시켜야 함.
    • 현업에서는 Bundle을 자주 이용한다는데 이해가 안감... 코드를 조금이라도 줄이려면 intent가 나을텐데..?

1.24 SubActivity.java
package com.example.mydatatmove;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class SubActivity extends Activity {

    private TextView text1,text2,text3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sub);

        //객체화
        text1 = (TextView)findViewById(R.id.text1);
        text2 = (TextView)findViewById(R.id.text2);
        text3 = (TextView)findViewById(R.id.text3);

        Intent intent = getIntent();

        // (방식1) intent 를 받아준다.

//        String name = intent.getStringExtra("name");
//        String age = intent.getStringExtra("age");
//        String b_day = intent.getStringExtra("b_day");

        // (방식2) bundle 을 받아준다.
        Bundle bundle = intent.getExtras();
        String name = bundle.getString("name");
        String age = bundle.getString("age");
        String b_day = bundle.getString("b_day");


        text1.setText(name);
        text2.setText(age);
        text3.setText(b_day);

    }
}


설명: 객체화는 당연한것! 또한 맨밑의 text.setText();는 앞의 MainActivity에서 getText();해주었으므로 받아주려면 당연히 필요!
서브엑티비티는 역시 받아주는 곳 이므로, intent냐 bundle이냐는 정하기 나름. 두 방식 모두 getString으로 받아준다는 점은 동일.

1.25 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mydatatmove">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <activity android:name=".SubActivity"/>

    </application>

</manifest>


설명: 화룡정점, 엑티비티 추가.


1. MySharedPreferences
1.1 결과
사진5 - 초기 화면
사진6 - 데이터 입력후 모습


설명: 이번 프로젝트는 '데이터 저장'에 포커스가 맞춰져있다. 내가 입력한 데이터가 어플리케이션 종료 후에도 유지 되어있어야 하는것이다.
예시로, 위에 쓰인 이름과 증가된 숫자 데이터는 내가 이걸 꺼도 다시 켰을때 저장되어있어야 한다.

1.2 코드
1.21 activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">

        <EditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="name input"
            android:textSize="25dp"/>

        <TextView
            android:id="@+id/value"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="30dp"
            android:gravity="center"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center">

            <Button
                android:id="@+id/btn_value"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Value++"
                android:textSize="20dp"/>

            <Button
                android:id="@+id/btn_reset"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Reset"
                android:textSize="20dp"/>

        </LinearLayout>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>


1.23 MainActivity (나쁜 예)

******나쁜예시이다. 절대 따라하지 마시라. (다만 각주를 통한 필기 부분은 중요하다.)

package com.example.mysharedpreferences;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

//Shared Preferences?
// (1) 일반적으로 정보들을 저장할떄 DB를 사용하지만, 간단한 것들을 위해 이를 사용하려면 오히려 낭비가 된다.
// (3) 이러한 간단한 정보를 파일형식으로 디바이스에 저장하는 용도로는 preferences 를 사용한다.
// (4) 이 기능으로 디바이스가 종료되거나 혹은 휴대폰은 재시작하여도 데이터가 계속 유지 될수있다.
// (5) 간단한 세이브 파일 용도로 사용되기에 아주 적합하다.
// (6) Bundle 과 같이 key 값과 value 값으로 저장하여 다른 엑티비티로 전달하는것도 가능하다. (key 와 value 는 어쩐지 id와 유사하구나)

//프로젝트 목표: 
// (1) value 버튼을 누를때마다 1씩 증가, 껐다가 다시켜도 그 숫자는 유지되어야한다.
// (2) 이름을 입력후 껐다가 다시켜도 그 글자는 유지되어야 한다.


설명: 프로젝트 목표의 두번째인 이름저장 부분을 나는 해결하지 못했다. 가장 큰 이유는 value++을 위해 생성한 int 와 동일 선상에서 놓고 해결하려 했기 때문이다.

public class MainActivity extends AppCompatActivity {


    private Button btn_value, btn_reset;
    private TextView value;
    private EditText edit_name;

    private int n = 0;
    private String t = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 앱이 최초로 실행될때 저장된 값을 로드한다.
        SharedPreferences pref = getSharedPreferences("Save",Context.MODE_PRIVATE);

        // 맨밑에 이미 key 와 value 값 설정이 끝나있어 호출이 가능한것이다.
        n = pref.getInt("save",0);
        t = pref.getString("name",null);


        //객체화
        value = (TextView)findViewById(R.id.value);
        btn_value = (Button)findViewById(R.id.btn_value);
        btn_reset = (Button)findViewById(R.id.btn_reset);
        edit_name = (EditText)findViewById(R.id.edit_name);

        t = edit_name.getText().toString();

        //value 안에 text 를 넣자.
        value.setText(n + "");

        //각 버튼에 기능 추가
        btn_value.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                n++;
                value.setText(n + "");
            }
        });

        btn_reset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                n = 0;
                value.setText(n + "");
            }
        });




    }

    //'어플리케이션이 종료될때' 시범 실행
    @Override
    protected void onDestroy() {
        Log.i("Destroy","종료");
        super.onDestroy();

        //프로그램이 종료될때 SharedPreferences 객체에 저장
        SharedPreferences sharedPreferences
                = getSharedPreferences("Save", Context.MODE_PRIVATE);
        // = getSharedPreferences(매개변수 파일명, 매개변수 파일에 대한 모드 ('0' 또는 'Context.MODE_PRIVATE' 적는다.);

        //edit 이라는 객체를 생성한다.
        SharedPreferences.Editor edit = sharedPreferences.edit();

        edit.putInt("save",n);
        edit.putString("name",t);
        //edit.putInt("키값",벨류값); -> 맨위에 저장데이터를 로드할때 이용하니 아주 중요하다.
        // 굳이 비유하자면 handler 의 what 이라고 할수있다!

        //commit을 하지 않으면 저장되지 않는다. 화룡정점이라는 의미.
        edit.commit();
    }
}

1.24 MainActivity (좋은 예)

******좋은예시이다. 반드시 따라하시라.

package com.example.mysharedpreferences;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {

    private TextView value;
    private EditText editText;
    private Button btn_value,btn_reset;

    private int n = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SharedPreferences pref = getSharedPreferences("Save",Context.MODE_PRIVATE);

        n = pref.getInt("save",-1);
        String name = pref.getString("saveName","");


        //객체화
        value = (TextView)findViewById(R.id.value);
        btn_value = (Button)findViewById(R.id.btn_value);
        btn_reset = (Button)findViewById(R.id.btn_reset);
        editText = (EditText)findViewById(R.id.editText);

        editText.setText(name);
        value.setText(n + "");

        btn_value.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                n++;
                value.setText(n + "");
            }
        });

        btn_reset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                n = 0;
                value.setText(n + "");
            }
        });
    }

    //어플리케이션이 종료될때 자동으로 실행되는 메소드
    @Override
    protected void onDestroy() {
        Log.i("Destroy","종료");
        super.onDestroy();

        //프로그램이 종료 될때
        //SharedPreferences 객체에 저장.

        SharedPreferences sharedPreferences
                 = getSharedPreferences("Save", Context.MODE_PRIVATE);
        //1.매개변수 -> 파일명
        //2.매개변수 -> 파일에대한 모드.. 0 아니면 Context.MODE_PRIVATE을 적는다..

        SharedPreferences.Editor edit = sharedPreferences.edit();

        edit.putInt("save",n);
        edit.putString("saveName",editText.getText().toString());


        edit.commit();// 저장기능. commit을 하지 않으면 저장되지 않는다.

    }
}


설명: 중요한 요소들을 차례대로 나열해보았다:
(1) SharedPreferences는 왠만해선 onDestory와 함께 다닌다.

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

- onDestory는 대단한게 아니다. 엑티비티의 일생, 즉 '생명주기'중에서도 마지막 부분인데, 간단히 말해 '어플을 껐을때' 이다. 이 프로젝트의 핵심 목표는 '어플을 껐다가 다시켜도 데이터를 저장시켜라' 아닌가, 그렇다면 데이터 저장 메소드도 당연히 '어플을 껐을때' 하위 부분에 종속되는것이 옳다.

(2) edit.put (키값과 벨류값);

        edit.putInt("save",n);
        edit.putString("saveName",editText.getText().toString());

        edit.commit();// 저장기능. commit을 하지 않으면 저장되지 않는다.

- handler에서 저장한 내용을 호출할때 index, 즉 what 을 이용하지 않았는가? 같은 개념이다. 더 쉽게 말하자면 id쯤 되겠다. 다만 id라고 너무 우기는것도 좀 그런게, 본인이 그렇게 생각했다가 String 부분에서 큰 코 다쳤기때문이다.
int부분은 n 이라는 매개변수를 value값으로 입력하기에 확실히 id의 느낌이 강한 반면, String은 그냥 멤버변수(거기다 메소드까지 추가해서) 그대로 value값에 넣어버린다 (...)

(3) pref.get (키값과 벨류값);

        n = pref.getInt("save",-1);
        String name = pref.getString("saveName","");

- 위 edit에 put(입력)을 했다면 당연히 get(호출)이 나와야 하는법. 다만 int의 경우 value값에 0을 넣으면 되는 반면, 이와 착각해서 string에는 null을 넣는 바보같은 행위는 지양하자 (필자가 그렇게 했다 ㅎㅎ..).

반응형