본문 바로가기
안드로이드 자바

[JAVA][Android] SharedPreferences에 객체 저장하기

by teamnova 2024. 4. 27.

 

안녕하세요.

오늘은 SharedPreferences에 객체 데이터를 저장하는 방법에 대해 공부할 수 있도록, 간단한 메모를 SharedPreferences에 저장하고 저장된 메모를 다시 불러와서 리사이클러뷰에 띄워주는 예제를 만들어보겠습니다. 

 

아래 작성된 예제에는 메모를 저장하고 다시 불러와서 조회하는 기능 밖에 없지만, 몇 가지 기능을 추가한다면 수정하고 삭제하는 기능도 만들 수 있습니다.

 

안드로이드에는 간단한 데이터를 키-값 쌍의 형태로 이루어진 파일에 저장할 수 있는 SharedPreferenses API가 있습니다.

해당 API가 제공하는 메서드를 사용하면 boolean, int, long, float, String 타입의 데이터를 저장하고 가져올 수 있습니다.

 

그러나 데이터를 객체로 관리하는 경우 SharedPreferenses에 바로 저장할 수 없기 다른 방법을 찾아야 합니다.

여러가지 방법이 있지만, 이 글에서는 Gson 라이브러리를 활용하여 객체 데이터를 JSON 형태의 String 데이터으로 변환하여 SharedPreferences에 저장하고자 합니다.

 

먼저 시연 영상 입니다.

 

 

1. Gson 라이브러리 의존성 추가

Gson 이란 구글에서 개발한 오프소스로 JAVA에서 JSON을 파싱하고 생성하기 위해 사용되는 라이브러리입니다.

Gson 라이브러리를 사용하기 위해 build.gradle 에 의존성을 추가합니다.

implementation 'com.google.code.gson:gson:2.10.1'

 

 

2. Memo 클래스

 

다음으로 메모 데이터를 관리하기 위한 Memo 클래스입니다.

import java.text.SimpleDateFormat;
import java.util.Date;

// 메모를 최근에 작성한 순서로 정렬하기 위해 Comparable을 implements 합니다.
public class Memo implements Comparable<Memo> {
    private String key; // 키값
    private String title; // 제목
    private String contents; // 내용
    private long editTime; // 작성일시

    public Memo(String title, String contents) {
        this.title = title;
        this.contents = contents;
        this.editTime = System.currentTimeMillis(); 
        // 쉐어드에 저장하고 불러올 때 사용할 키값은 최초 생성시간을 String으로 변환하여 사용했습니다.
        this.key = String.valueOf(this.editTime); 
    }

// compareTo 메서드를 override 하여 각 객체의 작성일시를 비교하도록 하였습니다.
    @Override
    public int compareTo(Memo memo) {
        if (this.editTime < memo.editTime) {
            return -1;
        } else if (this.editTime > memo.editTime) {
            return 1;
        }
        return 0;
    }

// long 타입으로 저장된 작성일시를 화면에 띄워줄 때 String 타입으로 변환하기 위한 메서드입니다.
    public String getTimeStr() { // ex) 2024.04.01 16:24
        Date date = new Date(this.editTime);
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm");
        return formatter.format(date);
    }

// 아래는 일반적인 getter 와 setter 입니다.
    public String getKey() {
        return key;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContents() {
        return contents;
    }

    public void setContents(String contents) {
        this.contents = contents;
    }

    public long getEditTime() {
        return editTime;
    }

    public void setEditTime(long editTime) {
        this.editTime = editTime;
    }

}

 

2.  SharedPreferencesHelper 클래스

SharedPreferences 사용을 돕기 휘안 Helper 클래스를 만들었습니다.

import android.content.Context;
import android.content.SharedPreferences;

import com.google.gson.Gson;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class SharedPreferencesHelper {
    private final SharedPreferences memoPref;
    private final SharedPreferences.Editor memoEditor;
    private final Gson gson;

    public SharedPreferencesHelper(Context mContext) {
        this.memoPref = mContext.getSharedPreferences("memo", 0);
        this.memoEditor = memoPref.edit();
        //Gson 객체를 생성합니다.
        this.gson = new Gson();
    }

//키값을 사용하여 쉐어드에 저장된 특정 메모를 가져오기 위한 메서드 입니다.
    public Memo getMemo(String key) {
        String json = memoPref.getString(key, "");
        if (json.isEmpty()) return null;
        return gson.fromJson(json, Memo.class);
    }

//메모를 쉐어드에 저장하기 위한 메서드 입니다.
    public void putMemo(Memo memo) {
        String json = gson.toJson(memo);
        memoEditor.putString(memo.getKey(), json);
        memoEditor.apply();
    }

//memo.xml 파일에 저장된 모든 메모를 리스트로 반환하기 위한 메서드입니다.
    public List<Memo> getMemoList() {
        List<Memo> list = new ArrayList<>();

        Collection<String> colKey = memoPref.getAll().keySet();
        for (String key : colKey) {
            list.add(getMemo(key));
        }
        // 메모를 최신순으로 정렬합니다.
        // Memo 클래스에서 오버라이드 했던 compareTo 메서드가 여기서 활용됩니다.
        list.sort(Collections.reverseOrder());

        return list;
    }
}

 

3.  MainActivity 작성

MainActicity에는 작성되어 있는 메모를 보여줄 RecyclerView가 있습니다.

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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/btn_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        android:src="@drawable/round_add_24"
        android:contentDescription="추가 버튼" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

MainActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private SharedPreferencesHelper pref;
    MemoAdapter adapter;
    List<Memo> dataList;

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

        pref = new SharedPreferencesHelper(this);

        FloatingActionButton btnWrite = findViewById(R.id.btn_add);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);


        adapter = new MemoAdapter();
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(
        	new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        );

        btnWrite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, WriteActivity.class);
                startActivity(intent);
            }
        });
    }

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

        dataList = pref.getMemoList();
        adapter.setDataList(dataList);
    }
}

 

4. 리사이클러뷰에서 사용할 아이템 레이아웃 작성

item_memo.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardUseCompatPadding="true"
    app:contentPadding="16dp"
    app:cardBackgroundColor="@color/white"
    app:strokeColor="@color/design_default_color_primary">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="제목"
            android:textStyle="bold"/>
        <TextView
            android:id="@+id/tv_contents"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="내용" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="2024.03.01"
            android:textAlignment="textEnd"/>
    </LinearLayout>

</com.google.android.material.card.MaterialCardView>

 

5. MainActivity의 리사이클러뷰에서 사용할 MemoAdapter & MemoViewHolder 작성

import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class MemoAdapter extends RecyclerView.Adapter<MemoAdapter.MemoViewHolder> {

    private List<Memo> dataList;

    @SuppressLint("NotifyDataSetChanged")
    public void setDataList(List<Memo> dataList) {
        this.dataList = dataList;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public MemoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(R.layout.item_memo, parent, false);
        return new MemoViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MemoViewHolder holder, int position) {
        Memo memo = dataList.get(position);
        holder.setMemo(memo);
    }

    @Override
    public int getItemCount() {
        return dataList == null ? 0 : dataList.size();
    }

    class MemoViewHolder extends RecyclerView.ViewHolder {

        TextView tvTitle;
        TextView tvContents;
        TextView tvTime;

        public MemoViewHolder(@NonNull View itemView) {
            super(itemView);
            this.tvTitle = itemView.findViewById(R.id.tv_title);
            this.tvContents = itemView.findViewById(R.id.tv_contents);
            this.tvTime = itemView.findViewById(R.id.tv_time);
        }

        public void setMemo(Memo memo) {
            tvTitle.setText(memo.getTitle());
            tvContents.setText(memo.getContents());
            tvTime.setText(memo.getTimeStr());
        }
    }
}

 

6. 메모 작성을 위한 WriteActivity 생성

activity_write.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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".WriteActivity">

    <EditText
        android:id="@+id/et_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:background="@color/material_dynamic_tertiary95"
        android:hint="제목을 입력해주세요"
        android:inputType="text"
        android:padding="8dp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_contents"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="16dp"
        android:background="@color/material_dynamic_tertiary95"
        android:ems="10"
        android:gravity="start|top"
        android:hint="내용을 입력해주세요"
        android:inputType="textMultiLine"
        android:padding="8dp"
        app:layout_constraintBottom_toTopOf="@+id/btn_save"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_title" />

    <Button
        android:id="@+id/btn_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="저장"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

WriteActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

public class WriteActivity extends AppCompatActivity {

    private SharedPreferencesHelper pref;

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

        pref = new SharedPreferencesHelper(this);

        EditText etTitle = findViewById(R.id.et_title);
        EditText etContents = findViewById(R.id.et_contents);
        Button btnSave = findViewById(R.id.btn_save);

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String title = etTitle.getText().toString();
                String contents = etContents.getText().toString();
                Memo memo = new Memo(title, contents);
                pref.putMemo(memo);

                Intent intent = new Intent(WriteActivity.this, MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            }
        });
    }
}