728x90

Android Room 예제를 검색하면 가장 많이 검색되는 것이 Simple Note 를 다룬 것이다.

아래 동영상 이외에도 대부분의 게시글, 동영상을 보면 모두 Simple Note를 약간씩 다르게 다루고 있지만 본질은 모두 동일하더라.

https://www.youtube.com/watch?v=xPPMygGxiEo 

 

이 자료를 토대로 하여 코드를 약간 손을 보고 디자인 부분을 수정하고 검색 기능을 추가하여 자료를 정리했다.

- CoordinatorLayout 과 MaterialToolbar 사용

- Menu SearchView 기능 추가 및 Room Database 검색 기능 추가

- ListAdapter 와 RecyclerView.Adapter 두가지 구현

- View Binding 적용

 

package com.link2me.android.notesample.view.activity;
 
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
 
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
 
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.link2me.android.notesample.R;
import com.link2me.android.notesample.databinding.ActivityMainBinding;
import com.link2me.android.notesample.model.Note;
import com.link2me.android.notesample.view.adapter.NoteViewAdapter;
import com.link2me.android.notesample.viewmodel.MyFactory;
import com.link2me.android.notesample.viewmodel.NoteViewModel;
 
import java.util.List;
 
public class MainActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    private Context mContext;
    private ActivityMainBinding binding;
 
    public static final int ADD_NOTE_REQUEST = 1;
    public static final int EDIT_NOTE_REQUEST = 2;
 
    private NoteViewModel viewModel;
 
    private RecyclerView mRecyclerView;
    //private NoteAdapter mAdapter;
    private NoteViewAdapter mAdapter;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
 
        mContext = MainActivity.this;
 
        initView();
    }
 
    private void initView() {
        // 동기식 데이터 처리에서는 순서가 중요함.
        setToolbar();
        buildRecyclerView();
        initViewModel();
        setButtons();
    }
 
    private void setToolbar() {
        // activity에 툴바를 추가하려면 우선 기본 앱바(AppBar)부터 비활성화해야 한다.
        // 기존 actionBar는 theme 에서 NoActionBar로 설정하여 제거하고 툴바를 생성한다.
        setSupportActionBar(binding.mainToolbar);
        // Toolbar의 왼쪽에 버튼을 추가하고 버튼의 아이콘을 바꾼다.
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_dehaze_24);
 
        // 타이틀명 변경
        if (getSupportActionBar() != null) {
            getSupportActionBar().setTitle("TODO LIST");
            binding.mainToolbar.setTitleTextColor(Color.WHITE);
        }
 
    }
 
    private void initViewModel() {
        // viewModel init
        //viewModel = new ViewModelProvider(this, new MyFactory(getApplication())).get(NoteViewModel.class);
        viewModel = new ViewModelProvider(this).get(NoteViewModel.class);
 
        // viewModel observe
        viewModel.getAllNotes().observe(thisnew Observer<List<Note>>() {
            @Override
            public void onChanged(@Nullable List<Note> notes) {
                //mAdapter.submitList(notes);
                mAdapter.setNotes(notes);
                for(Note note:notes){
                    Log.e(TAG,note.getId() + ", " + note.getTitle() + ", " + note.getDescription() + ", " + note.getPriority());
                }
                Log.e(TAG, "note_db size : " + notes.size());
            }
        });
 
    }
 
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.e(TAG,"onNewIntent");
    }
 
    private void buildRecyclerView() {
        mRecyclerView = binding.recyclerView;
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
 
//        mAdapter = new NoteAdapter();
        mAdapter = new NoteViewAdapter();
 
        DividerItemDecoration decoration = new DividerItemDecoration(mContext, manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
    }
 
    private void setButtons() {
        // 안드로이드 디자인 지원 라이브러리에는 FloatingActionButton 클래스가 제공된다.
        FloatingActionButton buttonAddNote = binding.buttonAddNote;
        buttonAddNote.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, AddNoteActivity.class);
                startActivityForResult(intent, ADD_NOTE_REQUEST);
            }
        });
 
        new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,
                ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }
 
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                viewModel.delete(mAdapter.getNoteAt(viewHolder.getBindingAdapterPosition()));
                Toast.makeText(MainActivity.this"Note deleted", Toast.LENGTH_SHORT).show();
            }
        }).attachToRecyclerView(mRecyclerView);
 
        mAdapter.setOnItemClickListener(new NoteViewAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(Note note) {
                Intent intent = new Intent(MainActivity.this, AddNoteActivity.class);
                intent.putExtra(AddNoteActivity.EXTRA_ID, note.getId());
                intent.putExtra(AddNoteActivity.EXTRA_TITLE, note.getTitle());
                intent.putExtra(AddNoteActivity.EXTRA_DESCRIPTION, note.getDescription());
                intent.putExtra(AddNoteActivity.EXTRA_PRIORITY, note.getPriority());
                startActivityForResult(intent, EDIT_NOTE_REQUEST);
            }
        });
 
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
 
        if (requestCode == ADD_NOTE_REQUEST && resultCode == RESULT_OK) {
            String title = data.getStringExtra(AddNoteActivity.EXTRA_TITLE);
            String description = data.getStringExtra(AddNoteActivity.EXTRA_DESCRIPTION);
            int priority = data.getIntExtra(AddNoteActivity.EXTRA_PRIORITY, 1);
 
            Note note = new Note(title, description, priority);
            viewModel.insert(note);
 
            Toast.makeText(this"메모가 저장되었습니다.", Toast.LENGTH_SHORT).show();
        } else if (requestCode == EDIT_NOTE_REQUEST && resultCode == RESULT_OK) {
            int id = data.getIntExtra(AddNoteActivity.EXTRA_ID, -1);
 
            if (id == -1) {
                Toast.makeText(this"Note can't be updated", Toast.LENGTH_SHORT).show();
                return;
            }
 
            String title = data.getStringExtra(AddNoteActivity.EXTRA_TITLE);
            String description = data.getStringExtra(AddNoteActivity.EXTRA_DESCRIPTION);
            int priority = data.getIntExtra(AddNoteActivity.EXTRA_PRIORITY, 1);
 
            Note note = new Note(title, description, priority);
            note.setId(id);
            viewModel.update(note);
 
            Toast.makeText(this"Note updated", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this"Note not saved", Toast.LENGTH_SHORT).show();
        }
    }
 
    /***
     * 메뉴는 프로젝트의 res/menu 폴더에 저장되는 XML 리소스 형식으로 정의할 수 있다.
     * 메뉴는 루트 노드에서 menu 태그와 일련의 item 태그로 구성된다.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // 부모 activity 나 fragment 와 메뉴를 연관시켜야 하므로 항상 super.onCreateOptionsMenu를 호출해야 한다.
        super.onCreateOptionsMenu(menu);
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main_menu, menu);
 
        // 검색 메뉴 등록
        MenuItem searchItem = menu.findItem(R.id.action_search);
        SearchView searchView = (SearchView) searchItem.getActionView();
 
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                // 문자열 입력을 완료했을 때 문자열 반환
                if(query != null) {
                    searchDatabase(query);
                }
                return true;
            }
 
            @Override
            public boolean onQueryTextChange(String newText) {
                // 문자열이 변할 때마다 바로바로 문자열 반환
                if(newText != null) {
                    searchDatabase(newText);
                }
                return true;
            }
        });
 
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.delete_all_notes:
                viewModel.deleteAllNotes();
                Toast.makeText(this"All notes deleted", Toast.LENGTH_SHORT).show();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
 
    private void searchDatabase(String search){
        String searchQuery = "%" + search + "%";
 
        viewModel.searchDatabase(searchQuery).observe(thisnew Observer<List<Note>>() {
            @Override
            public void onChanged(List<Note> notes) {
                //mAdapter.submitList(notes);
                mAdapter.setNotes(notes);
            }
        });
    }
 
}

 

 

NoteViewModel.java

 
import android.app.Application;
 
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
 
import com.link2me.android.notesample.model.Note;
import com.link2me.android.notesample.repository.NoteRepository;
import com.link2me.android.notesample.view.activity.MainActivity;
 
import java.util.List;
 
public class NoteViewModel extends AndroidViewModel {
    private NoteRepository repository;
    private LiveData<List<Note>> allNotes;
 
    public NoteViewModel(@NonNull Application application) {
        super(application);
        repository = new NoteRepository(application);
        allNotes = repository.getAllNotes();
    }
 
    public void insert(Note note) {
        repository.insert(note);
    }
 
    public void update(Note note) {
        repository.update(note);
    }
 
    public void delete(Note note) {
        repository.delete(note);
    }
 
    public void deleteAllNotes() {
        repository.deleteAllNotes();
    }
 
    public LiveData<List<Note>> getAllNotes() {
        return allNotes;
    }
 
 
    public LiveData<List<Note>> searchDatabase(String searchQuery){
        return repository.searchDatabase(searchQuery);
    }
}

 

전체적인 폴더 구조는 아래와 같다.

 

전체 소스코드는 GitHub에 올려두었다.

https://github.com/jsk005/JavaProjects/tree/master/room_mvvm_search

 

GitHub - jsk005/JavaProjects: 활용 수준의 자바 기능 테스트

활용 수준의 자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

블로그 이미지

Link2Me

,
728x90

SQLite DB의 테이블 데이터를 가져오는 쿼리 작성시 약간 잘못 생각하면 엉뚱한 결과가 나오더라.

public ArrayList<String> getAllCateData(String category){
    ArrayList<String> sqliteDBData = new ArrayList<>();
    // Select All Query
    String selectQuery = "SELECT * FROM " + HISTORY_TABLE + " WHERE category= '" + category + "' ORDER BY datetime ASC";
    SQLiteDatabase db = mHelper.getWritableDatabase();
    Cursor cursor = db.rawQuery(selectQuery, null);
 
    System.out.println("cursor.getCount : "+cursor.getCount());
    System.out.println("selectQuery : "+ selectQuery);
 
    if(cursor != null)
        if(cursor.moveToFirst()){ // 반드시 이런 순서로 해야 문제가 없더라.
            do {
                // ArrayList에 추가
                int column_index = cursor.getColumnIndex(Column_history);
                sqliteDBData.add(cursor.getString(column_index));
            } while (cursor.moveToNext());
        }
    cursor.close();
    return sqliteDBData;
}
 

 

 전체 소스는 GitHub 에 올려진 코드를 참고하면 된다.

https://github.com/jsk005/JavaProjects/tree/master/sqlitedb

블로그 이미지

Link2Me

,
728x90

SharedPreferences 싱글톤 처리 코드이다.

SharedPreferences는 간단한 값을 저장할 때 주로 사용한다.
초기 설정 값이나 자동 로그인 여부 등 간단한 값을 저장할 때 DB를 사용하면 복잡하기 때문에 SharedPreferences를 사용하면 적합하다.
SharedPreferences는 어플리케이션에 파일 형태로 데이터를 저장한다. 데이터는 (key, value) 형태로 data/data/패키지명/shared_prefs 폴더 안에 xml 파일로 저장된다.
해당 파일은 어플리케이션이 삭제되기 전까지 보존된다.
SharedPreferences는 앱의 어디서든 전역적으로 사용하기 때문에 싱글톤 패턴을 사용해서 어디서든 접근 가능하게 만드는 것이 좋다.


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

public class PrefsHelper {
    public static final String PREFERENCE_NAME="pref";
    private Context mContext;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor prefsEditor;
    private static PrefsHelper instance;

    public static synchronized PrefsHelper init(Context context){
        if(instance == null)
            instance = new PrefsHelper(context);
        return instance;
    }

    private PrefsHelper(Context context) {
        mContext = context;
        prefs = mContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE );
        prefsEditor = prefs.edit();
    }

    public static String read(String key, String defValue) {
        return prefs.getString(key, defValue);
    }

    public static void write(String key, String value) {
        prefsEditor.putString(key, value);
        prefsEditor.commit();
    }

    public static Integer read(String key, int defValue) {
        return prefs.getInt(key, defValue);
    }

    public static void write(String key, Integer value) {
        prefsEditor.putInt(key, value).commit();
    }

    public static boolean read(String key, boolean defValue) {
        return prefs.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }
}


사용법은

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intro);
        mContext = Intro.this;
        PrefsHelper.init(getApplicationContext()); // 한번만 실행하면 된다.
        // 네트워크 연결 검사
        if(Utils.NetworkConnection(mContext) == false) Utils.NotConnected_showAlert(mContext,this);
        checkPermissions();
    }


// 자동 로그인 체크 검사 (읽기)
userID = PrefsHelper.read("userid","");
userPW = PrefsHelper.read("userpw","");


// 쓰기

PrefsHelper.write("userid",userID);

PrefsHelper.write("userpw",userPW);



참고하면 좋은 자료

https://link2me.tistory.com/1528  Java Singleton Design Pattern


https://link2me.tistory.com/1821  Kotlin Singleton 코드 예제


https://link2me.tistory.com/1033  Android Preference

블로그 이미지

Link2Me

,
728x90

Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
        at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:267)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:323)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)


RoomDatabase 를 사용하는 방법에 대해 <오준석의 생존코딩> Morden Android 유투브 강좌를 듣고 구글링을 해서 내용을 좀 더 알게된 걸 적어둔다.


Room은 구글에서 만든 공식 ORM(Object-relational mapping)이라고 할 수 있으며 여러가지 강력한 기능들을 지원하고 있다. Room 지속성 라이브러리는 SQLite를 완벽히 활용하면서 강력한 데이터베이스 액세스를 지원하는 추상화 계층을 SQLite에 제공한다.



보다 자세한 내용은 https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#0 를 참조한다.

위 URL 마지막에 나오는 https://github.com/googlecodelabs/android-room-with-a-view 자료를 받아서 테스트 해본다.


Project build.gradle

buildscript {
    ext.kotlin_version = '1.3.71'
    repositories {
        google()
        jcenter()
       
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
       
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}



앱 build.gradle 추가 사항

Google I/O 2018에서는 AndroidX 패키지를 소개하였다. 하지만 아직 28.x까지는 기존 패키지를 사용할 수 있다.

dependencies {
    def lifecycle_version = "2.2.0"

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.1.0'

    implementation 'androidx.room:room-runtime:2.2.5'
    annotationProcessor 'androidx.room:room-compiler:2.2.5'

    // https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ko
    // Lifecycle components (ViewModel and LiveData)
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
}


activitity_main.xml 파일 만들기

https://www.youtube.com/watch?v=pG6OkJ3rSjg 동영상을 보는게 좋다.



Todo.java

import androidx.room.Entity;
import androidx.room.PrimaryKey;


// @Entity 어노테이션이 지정된 클래스는 데이터베이스의 테이블을 정의하는데 사용된다.
@Entity
public class Todo {
    // 1. 엔티티 클래스 만들기
    // 먼저 데이터 구조를 정의할 엔티티를 클래스 형태로 만들어 준다.

    @PrimaryKey(autoGenerate = true)
    private int id;
    private String title;

    public Todo(String title) {
        this.title = title;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

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

    @Override
    public String toString() {
        return "Todo{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}


TodoDao.java

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface TodoDao {
    // 2. DAO 인터페이스 만들기

    @Query("SELECT * FROM Todo")
    LiveData<List<Todo>> getAll();

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    void insert(Todo todo);

    @Update
    void update(Todo todo);

    @Delete
    void delete(Todo todo);

    @Query("DELETE FROM Todo")
    void deleteAll();

}


AppDatabase.java

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Database(entities = {Todo.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    // 3. 데이터베이스 클래스 만들기
    // https://webcoding.tistory.com/ 참조하면 좋은 자료 많다.

    public abstract TodoDao todoDao(); // Dao 인터페이스
    private static volatile AppDatabase INSTANCE;

    private static final int NUMBER_OF_THREADS = 4;
    static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);

    // 인스턴스를 생성하여 반환
    public static AppDatabase getInstance(Context context){
        if(INSTANCE == null){
            synchronized (AppDatabase.class){
                if(INSTANCE == null){
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class,"Todo.db")
                            //.allowMainThreadQueries()
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}


MainViewModel.java

import android.app.Application;
import android.os.AsyncTask;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

public class MainViewModel extends AndroidViewModel {
    private AppDatabase db;

    public MainViewModel(@NonNull Application application) {
        super(application);
        db = AppDatabase.getInstance(application); // 데이터베이스 접근 인스턴스 사용하기
    }

    public LiveData<List<Todo>> getAll(){
        return db.todoDao().getAll();
    }

    public void insert(Todo todo){
        new InsertAsyncTask(db.todoDao()).execute(todo);
    }

    private static class InsertAsyncTask extends AsyncTask<Todo,Void, Void> {
        private TodoDao mTodoDao;

        public InsertAsyncTask(TodoDao todoDao) {
            this.mTodoDao = todoDao;
        }

        @Override
        protected Void doInBackground(Todo... todos) {
            mTodoDao.insert(todos[0]);
            return null;
        }
    }
}


MainActivity.java

viewModel = ViewModelProviders.of(this).get(MainViewModel.class);

ViewModelProviders.of()로 초기화하던 방식이 2.2.0에서 deprecated 되었으므로 아래와 같이 수정한다.

import android.os.Bundle;
import android.widget.EditText;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;

public class MainActivity extends AppCompatActivity implements ViewModelStoreOwner {
    private EditText mTodoEditText;
    private TextView mResultTextView;

    private ViewModelProvider.AndroidViewModelFactory viewModelFactory;
    private ViewModelStore viewModelStore = new ViewModelStore();
    private MainViewModel viewModel;

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

        mTodoEditText = findViewById(R.id.todo_edit);
        mResultTextView = findViewById(R.id.result_text);

        if(viewModelFactory == null){
            viewModelFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());
        }
        viewModel = new ViewModelProvider(this,viewModelFactory).get(MainViewModel.class);

        // UI 갱신
        viewModel.getAll().observe(this, todos -> {
            mResultTextView.setText(todos.toString());
        });

        // 버튼 클릭시 DB에 insert
        findViewById(R.id.add_btn).setOnClickListener(v -> {
            viewModel.insert(new Todo(mTodoEditText.getText().toString()));
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        viewModelStore.clear();
    }

    @Override
    public ViewModelStore getViewModelStore(){
        return viewModelStore;
    }
}


<오준석의 생존코딩 모던 안드로이드> 유투브 강좌로 부족한 부분은 https://github.com/tazihad/Android---Room-ViewModel-LiveData-RecyclerView-MVVM---Example 에 나오는 자료를 받고, 관련 유투브 강좌 https://www.youtube.com/watch?v=ARpn-1FPNE4 를 보면 도움이 될 것이다.


유투브 강좌를 듣고 따라한 코딩 소스를 첨부한다.

notesqlite-1.zip


https://guides.codepath.com/android/Room-Guide 를 보면 Room 사용법에 대해 조금 더 이해하는데 도움된다.


notesqlite-2.zip


@Entity(tableName = "note_table", indices = @Index(value = {"title"}, unique = true))
public class Note {

    @PrimaryKey(autoGenerate = true)
    private int id;
    private String title; // 중복 입력 체크
    private String description;
    private int priority;

    public Note(String title, String description, int priority) {
        this.title = title;
        this.description = description;
        this.priority = priority;
    }
}

@Dao
public interface NoteDao {

    // 중복 값 입력시 새로운 값으로 변경됨.
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(Note note);

    // 기존에 존재하는 제목 값으로 변경시 무시
    @Update(onConflict = OnConflictStrategy.IGNORE)
    void update(Note note);

    @Delete
    void delete(Note note);

    @Query("DELETE FROM note_table")
    void deleteAllNotes();

    @Query("SELECT * FROM note_table ORDER BY priority DESC")
    LiveData<List<Note>> getAllNotes();

}


중복 입력 체크 예시

출처 : https://stackoverflow.com/questions/46916388/android-room-inserts-duplicate-entities


@Entity(tableName = "cameras", indices = [Index(value = ["accountId","dvrId"], unique = true)])
public class CameraEntity {
    @PrimaryKey(autoGenerate = true)
    private int id;
    private Integer accountId;
    private Integer dvrId;
    private String vendorId;
}

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<CameraEntity> values);



@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
    List<User> findUsersBornBetweenDates(Date from, Date to);
}


출처 : https://developer.android.com/training/data-storage/room#java
@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

블로그 이미지

Link2Me

,
728x90

Android Memo 어플 소스를 RecyclerView 를 이용하여 수정했다.

SQLite 함수 일부분을 수정한 것은 같이 포함시켰다.


DBHelper.java 수정

public class DbHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME = "data2";
    public static final String TABLE_NAME = "comments_table";
    public static final String C_ID = "_id";
    public static final String TITLE = "title";
    public static final String TYPE = "type";
    public static final String DETAIL = "description";
    public static final String TIME = "time";
    public static final String DATE = "date";
    public static final int DATABASE_VERSION = 2;

    private final String createDB = "create table if not exists " + TABLE_NAME + " ( "
            + C_ID + " integer primary key autoincrement, "
            + TITLE + " text, "
            + DETAIL + " text, "
            + TYPE + " text, "
            + TIME + " text, "
            + DATE + " text)";

    public DbHelper(Context context){
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(createDB);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table " + TABLE_NAME);
    }

    public Cursor LoadSQLiteDBCursor() {
        SQLiteDatabase db = this.getReadableDatabase();
        db.beginTransaction();
        // Select All Query
        String selectQuery = "SELECT _id,title,type,description,time,date FROM " + TABLE_NAME;
        Cursor cursor = null;
        try {
            cursor = db.rawQuery(selectQuery, null);
            db.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            db.endTransaction();
        }
        return cursor;
    }

}


MainActivity.java 수정

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

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

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    Context context;

    SQLiteDatabase db;
    DbHelper mDbHelper;

    private RecyclerView listView;
    private ArrayList<Memo_Item> memoItemList= new ArrayList<>(); // SQLite에서 가져온 원본 데이터 리스트
    RecyclerView.Adapter listViewAdapter; // ListViewAdapter 대신 RecyclerView.Adapter
    RecyclerView.LayoutManager layoutManager;

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

        listView = (RecyclerView)findViewById(R.id.commentslist);
        listView.setHasFixedSize(true);

        memoItemList.clear(); // 데이터 초기화
        mDbHelper = new DbHelper(this);
        db= mDbHelper.getReadableDatabase();
        db.beginTransaction();

        Cursor cursor = mDbHelper.LoadSQLiteDBCursor();
        try {
            cursor.moveToFirst();
            System.out.println("SQLiteDB 개수 = " + cursor.getCount());
            while (!cursor.isAfterLast()) {
                addGroupItem(cursor.getLong(0),cursor.getString(1),cursor.getString(2),
                        cursor.getString(3),cursor.getString(4),cursor.getString(5));
                cursor.moveToNext();
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
                db.endTransaction();
            }
        }

        // Set Layout Manager
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        listView.setLayoutManager(layoutManager);

        listViewAdapter = new ListViewAdapter(memoItemList, this); // Adapter 생성
        listView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
    }

    public void addGroupItem(Long uid, String title, String memo_type, String detail, String time, String date){
        Memo_Item item = new Memo_Item();
        item.setUid(uid);
        item.setTitle(title);
        item.setMemo_type(memo_type);
        item.setDetail(detail);
        item.setTime(time);
        item.setDate(date);
        memoItemList.add(item);
    }

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch(item.getItemId()) {
            case R.id.action_new:
                Intent openCreateNote = new Intent(MainActivity.this, CreateNote.class);
                startActivity(openCreateNote);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

}


Memo_Item.java 클래스 만들기

public class Memo_Item {
    private Long uid;
    private String title; // 제목
    private String memo_type; // 메모 타입
    private String detail; // 내용
    private String time; // 시간
    private String date; // 작성일자

    public Memo_Item() {

    }

    public Memo_Item(Long uid, String title, String memo_type, String detail, String time, String date) {
        this.uid = uid;
        this.title = title;
        this.memo_type = memo_type;
        this.detail = detail;
        this.time = time;
        this.date = date;
    }

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

    public String getTitle() {
        return title;
    }

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

    public String getMemo_type() {
        return memo_type;
    }

    public void setMemo_type(String memo_type) {
        this.memo_type = memo_type;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}


ListViewAdapter 어댑터 클래스 만들기

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;

import java.util.ArrayList;

public class ListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    Context mContext;
    private ArrayList<Memo_Item> IvList;
    int Position;

    public ListViewAdapter(ArrayList<Memo_Item> items, Context context) {
        IvList = items;
        mContext = context;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_entry, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        // ListView의 getView 부분을 담당하는 메소드
        ((ViewHolder) holder).onBind(IvList.get(position));
    }

    @Override
    public int getItemCount() {
        return IvList.size(); // 데이터 개수 리턴
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTitle;
        public TextView memoType;
        public TextView mDetail;
        public ImageView mImage;
        public TextView mTime;
        public TextView mDate;

        public ViewHolder(View itemView) {
            super(itemView);
            // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
            mTitle = itemView.findViewById(R.id.title);
            memoType = itemView.findViewById(R.id.type);
            mDetail = itemView.findViewById(R.id.Detail);
            mImage = itemView.findViewById(R.id.alarmImage);
            mTime = itemView.findViewById(R.id.time);
            mDate = itemView.findViewById(R.id.date);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Position = getAdapterPosition();
                    Intent intent = new Intent(mContext, View_Note.class);
                    intent.putExtra(mContext.getString(R.string.row_id),IvList.get(Position).getUid());
                    Log.e("rowID", String.valueOf(IvList.get(Position).getUid()));
                    mContext.startActivity(intent);
                }
            });
        }

        public void onBind(Memo_Item item) {
            mTitle.setText(item.getTitle());
            memoType.setText(item.getMemo_type());
            mDetail.setText(item.getDetail());
            int resourceId = R.drawable.ic_action_alarms;
            Glide.with(mContext).load(resourceId).into(mImage);
            mTime.setText(item.getTime());
            mDate.setText(item.getDate());
        }
    }
}


activity_main.xml 수정

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        >

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/commentslist"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

</RelativeLayout>


build.gradle 수정

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.tistory.link2me.memo"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
    implementation 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
}



나머지는 수정한 사항이 없다.

기존 소스코드를 이렇게 수정하면 된다.


memo_src.zip



블로그 이미지

Link2Me

,
728x90

안드로이드 메모 어플을 https://github.com/DomHeal/Android-Memo-Application 에서 받아서 코드를 API 28 (Android 9) 에서 동작하도록 수정한 코드이다.

ListView 에 일렬로 보여주는 아주 간단한 메모 코드다. SQLite DB를 사용하여 데이터를 로컬 DB에 저장하고 보여주는 코드이므로 달력 형태로 보여주는 건 아니다.

간단하게 SQLite DB 사용법 익히는 용도로 좋은 예제인거 같다.


memo.zip


앱 build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28


    defaultConfig {
        applicationId "com.tistory.link2me.memo"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.link2me.memo">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true" <!-- 서버와 http 통신할 경우 추가해야 정상동작 -->
        android:theme="@style/AppTheme">
        <activity android:name=".Alert"></activity>
        <activity android:name=".Edit_Note" />
        <activity android:name=".View_Note" />
        <activity android:name=".CreateNote" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <receiver
            android:name=".AlarmReceiver"
            android:enabled="true"
            android:exported="true" />
    </application>

</manifest>
 


MainActivity.java

import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    SQLiteDatabase db;
    DbHelper mDbHelper;
    ListView list;

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

        list = (ListView)findViewById(R.id.commentslist);
        mDbHelper = new DbHelper(this);
        db= mDbHelper.getWritableDatabase();
        final ImageView alarmImage = (ImageView) findViewById(R.id.alarmImage);

        String[] from = {mDbHelper.TITLE, mDbHelper.DETAIL, mDbHelper.TYPE, mDbHelper.TIME, mDbHelper.DATE};
        final String[] column = {mDbHelper.C_ID, mDbHelper.TITLE, mDbHelper.DETAIL, mDbHelper.TYPE, mDbHelper.TIME, mDbHelper.DATE};
        int[] to = {R.id.title, R.id.Detail, R.id.type, R.id.time, R.id.date};

        final Cursor cursor = db.query(mDbHelper.TABLE_NAME, column, null, null ,null, null, null);
        final SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.list_entry, cursor, from, to, 0);

        list.setAdapter(adapter);
        list.setOnItemClickListener(new AdapterView.OnItemClickListener(){
            public void onItemClick(AdapterView<?> listView, View view, int position,
                                    long id){
                Intent intent = new Intent(MainActivity.this, View_Note.class);
                intent.putExtra(getString(R.string.rodId), id);
                startActivity(intent);
            }

        });

    }

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch(item.getItemId()) {
            case R.id.action_new:
                Intent openCreateNote = new Intent(MainActivity.this, CreateNote.class);
                startActivity(openCreateNote);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

}


나마저 코드와 xml 파일은 첨부파일을 참조해서 프로젝트 생성하고 모듈 추가해서 코드를 덮어쓰거나 변형하여 덮어쓰기 하면 동작되는 걸 확인할 수 있다.

ListView 를 사용한 코드인데 RecyclerView 로 변경하는 건 나중에 기회되면 수정해서 올려볼 생각이다.


참고자료

안드로이드 모듈 추가 방법 https://link2me.tistory.com/1369 참조



블로그 이미지

Link2Me

,
728x90
SQLiteDB에 있는 자료를 읽어서 HashMap 메모리 상에 올리는 예제이다.

HashMap<String, SQLite_Item> sqliteDBMap = new HashMap<String, SQLite_Item>();
public void SQLiteDB2ArrayList(){
    sqliteDBMap.clear(); // 메모리 초기화
    sqLiteDBHandler = new SQLiteDBHandler(Main.this);
    
    SQLiteDatabase db = sqLiteDBHandler.getReadableDatabase();
    db.beginTransaction();
    
    Cursor cursor = sqLiteDBHandler.LoadSQLiteDBCursor();
    try {
        cursor.moveToFirst();
        System.out.println("SQLiteDB 개수 = " + cursor.getCount());
        while (!cursor.isAfterLast()) {
            SQLite_Item item = new SQLite_Item();
            item.setIdx(cursor.getString(0));
            item.setUserNM(cursor.getString(1));
            item.setMobileNO(cursor.getString(2));
            item.setTelNO(cursor.getString(3));
            item.setTeam(cursor.getString(4));
            item.setPosition(cursor.getString(5));
            item.setCheckState(cursor.getInt(6));
            // HashMap 에 추가
            sqliteDBMap.put(cursor.getString(0), item);
            cursor.moveToNext();
        }
        db.setTransactionSuccessful();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        cursor.close();
        db.endTransaction();
    }        
}


sqLiteDBHandler = new SQLiteDBHandler(context); // BroadcastReceiver 에서 사용할 때


블로그 이미지

Link2Me

,
728x90

안드로이드 SQLite DB 관련으로 알아두어야 할 사항이다.


rawQuery 를 이용하는 방법과 query를 이용하는 방법의 차이점이다.


rawQuery() Example

rawQuery() 메소드에 SELECT 구문을 넘겨 주기만 하면 된다.

인자로 idx 를 받아서 Where 조건문으로 일치하는 데이터 칼럼을 전부 가져오는 코드다.


 SQLiteDatabase db = getReadableDatabase(); // 읽기 가능한 데이터베이스

 Cursor cursor = db.rawQuery("select * from myTable where _idx = ?", new String[] { idx });

 SQLiteDatabase db = getReadableDatabase(); // 읽기 가능한 데이터베이스

 String sql = "SELECT idx, id, pw, name FROM member";

 Cursor cursor = db.rawQuery(sql, null);


query() Example

query() 메소드는 SELECT 구문의 각 부분을 쪼개 각 인자로 넘겨받고, 최종적으로는 SELECT문을 생성해 실행한다.

 Cursor cursor = db.query(DB_TABLE,
    new String[] { KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION },
    null, null, null, null, null);

 query() 가 받아서 처리하는 인자의 순서
 1. 대상 테이블 이름(DB_TABLE)
 2. 값을 가져올 컬럼 이름의 배열
    String[] columns ={
KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION};
 3. WHERE 구문. 물음표를 사용해 인자의 위치를 지정할 수 있다.

     "name=?"
 4. WHERE 구문에 들어가는 인자값

     String[] params ={"홍길동"};
 5. GROUP BY 구문
 6. ORDER BY 구문
 7. HAVING 구문
 테이블 이름을 제외한 각 값이 필요없는 경우라면 null을 지정한다.

 db.query(DBTable,columns,whereclause,whereargs,null,null,null);

 String query =
    "SELECT column1, (SELECT max(column1) FROM table1) AS max FROM table1 " +
    "WHERE column1 = ? OR column1 = ? ORDER BY column1";
 sqLiteDatabase.rawQuery(query, whereArgs);




SQLiteOpenHelper 클래스에서 제공하는 ContentVales 객체를 이용하여 DB를 update 하는 방법이다.

public void update(Integer idx, String name, String mobileNO, String officeNO) {
    SQLiteDatabase db = this.getWritableDatabase();
    ContentValues cv = new ContentValues();
    cv.put(Column_Name, name);
    cv.put(Column_mobileNO, mobileNO);
    cv.put(Column_officeNO, officeNO);
    db.update(TABLE_NAME, cv, "idx = ? ", new String[]{Integer.toString(idx)});
}


이 코드를 표준 SQL 문으로 입력한다면 아래와 같다.

public void update(Integer idx, String name, String mobileNO, String officeNO) {
    SQLiteDatabase db = this.getWritableDatabase();
    String sql ="Update "+TABLE_NAME+" SET name='"+name+"' mobileNO='"+mobileNO+"' officeNO='"+officeNO+"' where idx="+idx+"";
    db.execSQL(sql);
}



블로그 이미지

Link2Me

,
728x90

Android Studio SQLite DB에 대해 다뤄보자.


안드로이드에서 데이터를 저장하는 방법은 다음과 같은 것들이 있다.

SharedPreference

 key - value 으로 저장하여 모든 Intent에서 활용 가능

 주로 서버 로그인후 idx, userID 등의 정보를 저장하여 활용한다.

 앱이 삭제되면 저장된 정보는 삭제된다.

 Internal Storage

 안드로이드는 리눅스 운영체제를 수정하여 만들었으며,

 /data/data/어플리케이션패키지명/files 디렉토리에 저장한다.

 앱이 삭제되면 어플리케이션패키지명이 삭제되므로 이 공간에 저장된 파일들도

 삭제된다.

 External Storage

 외부저장공간은 탈착이 가능한 SD카드다.

 안드로이드 장치는 외부 저장 공간을 지원하므로

 외부 저장공간에 파일을 저장할 수 있다.

 누구나 읽을 수 있고 사용자에의해 변경될 수 있다.

 SQLite 데이터베이스

 구조화된 데이터를 DB에 저장한다.

 SQLite는 초경량 데이터베이스로 데이터를 디스크 파일에 저장한다.

 데이터 정의 명령어는 테이블을 생성/변경/삭제한다.

 데이터 조작 명령어는 데이터를 조회/추가/삭제/수정한다.


Cursor 사용

Cursor 인터페이스는 데이터베이스 쿼리에 의해서 반환된 result 셋에 임시적으로 데이터를 읽고 쓰기 위해서 액세스할 수 있는 기능을 제공하는 인터페이스다.

인자

내용 설명

 moveToFirst

커서가 쿼리(질의) 결과 레코드들 중에서 가장 처음에 위치한 레코드를 가리키도록 한다.

 moveToLast

 마지막 행으로 이동

 moveToNext

 다음 레코드로 커서를 이동

 moveToPrevious

 이전 레코드로 커서를 이동

 moveToPosition(int position)

 파라미터로 주어진 절대 행 위치로 이동한다. 성공적이면 true 반환

 getCount

 질의 결과값(레코드)의 갯수를 반환, 조건이 없으면 총개수 반환

 getColumnCount

 총 칼럼 개수를 반환

 getColumnIndex

 해당 이름의 칼럼의 0 베이스 칼럼 인덱스 값을 반환해준다.

 해당 이름의 칼럼이 존재하지 않으면 -1을 반환한다.

 getColumnIndexOrThrow

 해당 이름을 가지고 있는 칼럼의 0 베이스 인덱스 번호를 반환한다.
 해당 이름의 칼럼이 존재하지 않으면 IllegalArgumentException 을 발생시킨다.

 getColumnName

 특정 인덱스값에 해당하는 필드 이름을 반환

 getColumnNames

 칼럼의 이름들을 String(문자열) 배열 형태로 반환

 moveToPosition

 커서를 특정 레코드로 이동시킨다

 getPosition

 커서가 현재 가리키고 있는 위치를 반환

 getDouble

 해당 인덱스의 칼럼 값을 double 형으로 반환한다.

 close()

 열려있는 커서를 닫는다.


SQLite 데이터베이스의 특성상 하나의 테이블의 레코드를 읽어 오기 위해서는 커서(cursor)라는 것이 필요하다.

조건에 맞는 레코드를 한꺼번에 모두 가져올 수 없기 때문에 커서(cursor)를 이용해서 조작을 한다.

커서(cursor)는 현재 레코드를 가리킨다. 하나씩 이 커서(cursor)를 이동하면서 레코드 하나하나씩을 접근해서 가져온다. 이 커서 객체를 이용하여 get을 하게 되면 컬럼 번호에 맞게 데이터를 가져올 수 있다.

커서는 정방향이나 역방향으로 움직일 수 있다.


SQLite 는 구글 안드로이드 운영체제에 기본 탑재된 데이터베이스로 비교적 가벼운 데이터베이스다.


SQL 기본 문법은 http://www.w3schools.com/sql/default.asp 에서 익히면 된다.

SQLite 는 완전히 독립적이고 서버 기능을 제공하지 않으며, 트랜잭션을 지원하며 쿼리를 수행할 수 있는 표준 SQL 언어를 사용한다.

SQLite 만의 문법사항을 알아야만 편하다. 간단하게 SQLite 의 문법 사항을 알아보자.

ㅇ Data Type : Null, Integer(정수), Real(실수), Text(문자열), Blob

    - Integer : 부호있는 정수, 실제 값에 따라 1byte ~ 8bytes까지 가변적으로 저장됨

    - Boolean values are stored as integers 0 (false) and 1 (true).

    - Date and Time 데이터 타입은 없다. datetime은 입력되는 값에 따라 Text, Real, Integer 타입으로 저장됨

ㅇ JOIN : Left Outer Join, INNER Join 지원

ㅇ Rename Table, Add Column 지원 (Drop Column, Alter Column, Add Constraint 미지원)

    - 한번에 여러 Column 추가 안된다.

    - Join Update 를 지원하지 않는다.

ㅇ Grant and Revoke : 별도로 권한 부여 기능이 없고, OS 파일 시스템 권한을 사용


SQLite 에 대한 자세한 설명은 http://overoid.tistory.com/19 에 잘 설명되어 있다.

영문 원본을 읽어보려면 http://www.sqlite.org/datatype3.html 를 보면 된다.


http://www.androidhive.info/2011/11/android-sqlite-database-tutorial/ 에 좋은 예제가 있다.



초기 데이터가 많은 경우 DB 파일을 assets 에 저장해두고 copy 해서 SQLite 저장하는 것은 해보지 않았다.

서버 데이터를 가져오는 케이스에 대한 로직을 그려본다면 ...

서버에 있는 자료를 읽어서 SQLite DB에 저장하고, SQLite DB에 저장된 데이터를 읽어서 ArrayList에 저장하고 ListView 또는 RecyclerView 를 통해 보여주거나, 폰에 주소록을 저장할 수도 있다.

안드로이드에서 데이터베이스를 사용하려면

- SQLiteOpenHelper 를 사용하는 방법

- openOnCreateDatabase() 메소드로 데이터베이스 객체를 직접 생성하는 방법

둘 중 하나를 선택해야 한다.


안드로이드에 내장되어 있는 SQLite을 사용하기 위해서는 SQLiteOpenHelper 클래스를 이용해야 한다.
SQLiteOpenHelper 클래스는 데이터베이스를 생성하고 버전을 관리 할 수 있도록 도와주는 클래스이다.

SQLiteOpenHelper 클래스를 이용하면 SQL 기본 사용법을 알면 쉽게 처리할 수가 있다.


SQLiteOpenHelper 를 사용하는 방법으로 코드를 구현해 보자.

 === SQLiteDBHandler ===

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class SQLiteDBHandler extends SQLiteOpenHelper {
    public static final String TAG = "DBHelper";

    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "mCRM.db";
    public static final String TABLE_NAME = "PBbook";

    public static final String Column_Uid = "uid";
    public static final String Column_Name = "name";
    public static final String Column_mobileNO ="mobileNO";
    public static final String Column_officeNO ="officeNO";
    public static final String Column_profile_image = "profile_image";

    public SQLiteDBHandler(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String Member_Create = "create table "+ TABLE_NAME + " (" +
                "idx INTEGER PRIMARY KEY AUTOINCREMENT,"+
                "uid INTEGER not null," +
                "name TEXT not null," +
                "mobileNO TEXT not null," +
                "officeNO TEXT," +
                "profile_image TEXT);";
        db.execSQL(Member_Create);
        Log.v(TAG,"DB Created");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.w("LOG_TAG", "Upgrading database from version " + oldVersion + " to " + newVersion
                + ", which will destroy all old data");
        // KILL PREVIOUS TABLES IF UPGRADED
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
        onCreate(db); //새로 생성하기
        Log.v(TAG,"DB Updated");
    }

    public void InsertData(String uid,String name, String mobileNO, String officeNO,String profile_image) {
        SQLiteDatabase db = getWritableDatabase(); // 쓰기 가능한 데이터베이스를 가져와 입력

        // 이름 + 휴대폰번호 기준으로 중복 체크
        String query = "select idx from " + TABLE_NAME
                + " where " + Column_Name + "= '"+ name +"' and " + Column_mobileNO + "= '" + mobileNO + "'";
        Cursor cursor = db.rawQuery(query, null);
        cursor.moveToFirst(); // Cursor를 제일 첫행으로 이동
        if( cursor.getCount() == 0) {  // 중복이 없으면 저장하라.
            ContentValues cv = new ContentValues(); // 객체 생성
            cv.put(Column_Uid, uid);
            cv.put(Column_Name, name);
            cv.put(Column_mobileNO, mobileNO);
            cv.put(Column_officeNO, officeNO);
            cv.put(Column_profile_image, profile_image);
            db.beginTransaction();  // 대량건수 데이터 입력 처리를 고려
            try{
                long rowId = db.insert(TABLE_NAME, null, cv);
                if(rowId < 0){
                    throw new SQLException("Fail to Insert");
                }
                db.setTransactionSuccessful();
            } catch(Exception e){
                Log.i(TAG,e.toString());
            } finally {
                db.endTransaction();
                Log.v(TAG,"DB Inserted " + name + " uid =" + uid);
            }
        }
        cursor.close();
        db.close();
    }

    /* Get the first row Column_ID from the table */
    public int getFirstId() {
        int idToUpdate = 0;
        String query = "select idx from " + TABLE_NAME + " LIMIT 1";

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor res = db.rawQuery(query, null);

        if (null != res && res.getCount() > 0) {
            res.moveToFirst(); // Cursor를 제일 첫행으로 이동
            idToUpdate = res.getInt(0);
        }
        return idToUpdate;
    }

    /* Update the table row with Column_ID - id */
    public boolean updateDB(Integer idx, String name, String mobileNO, String officeNO) {
        Log.i(TAG, "Updating Column_ID : " + idx);
        ContentValues cv = new ContentValues();
        cv.put(Column_Name, name);
        cv.put(Column_mobileNO, mobileNO);
        cv.put(Column_officeNO, officeNO);

        SQLiteDatabase db = this.getWritableDatabase();
        db.update(TABLE_NAME, cv, "idx = ? ", new String[]{Integer.toString(idx)});
        return true;
    }

    /* Delete the row with Column_ID - id from the employees table */
    public Integer deleteRow(Integer idx) {
        SQLiteDatabase db = this.getWritableDatabase();
        return db.delete(TABLE_NAME, "idx = ? ", new String[]{Integer.toString(idx)});
    }

    public int getTableRowCount() {
        String countQuery = "SELECT * FROM " + TABLE_NAME;
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(countQuery, null);
        Log.i(TAG, "Total Row : " + cursor.getCount());
        cursor.close();
        return cursor.getCount();
    }

    // Getting All Contacts
    public List<Address_Item> getAllPBook() {
        List<Address_Item> pbookData = new ArrayList<Address_Item>();
        // Select All Query
        String selectQuery = "SELECT  * FROM " + TABLE_NAME;

        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);

        // looping through all rows and adding to list
        if (cursor.moveToFirst() && cursor.getCount() > 0) {
            do {
                Address_Item contact = new Address_Item();
                contact.setUid(cursor.getString(1));
                contact.setName(cursor.getString(2));
                contact.setMobileNO(cursor.getString(3));
                contact.setOfficeNO(cursor.getString(4));
                contact.setProfile_image(cursor.getString(5));
                // Adding contact to list
                pbookData.add(contact); // DB에서 받아온 값을 ArrayList에 Add
            } while (cursor.moveToNext());
        }
        cursor.close();
        return pbookData; // return contact list
    }
}

 === Address_Item.java ===

 public class Address_Item {
    // PersonData 정보를 담고 있는 객체 생성
    private String profile_image; // 이미지 경로를 String으로 받기 위해서
    private String uid;
    private String name;
    private String mobileNO;
    private String officeNO;
    boolean checkBoxState;

    public Address_Item() {
    }

    public Address_Item(String profile_image, String uid, String name, String mobileNO, String officeNO, boolean checkBoxState) {
        this.profile_image = profile_image;
        this.uid = uid;
        this.name = name;
        this.mobileNO = mobileNO;
        this.officeNO = officeNO;
        this.checkBoxState = checkBoxState;
    }

    public String getProfile_image() {
        return profile_image;
    }

    public void setProfile_image(String profile_image) {
        this.profile_image = profile_image;
    }

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobileNO() {
        return mobileNO;
    }

    public void setMobileNO(String mobileNO) {
        this.mobileNO = mobileNO;
    }

    public String getOfficeNO() {
        return officeNO;
    }

    public void setOfficeNO(String officeNO) {
        this.officeNO = officeNO;
    }

    public boolean isCheckBoxState() {
        return checkBoxState;
    }

    public void setCheckBoxState(boolean checkBoxState) {
        this.checkBoxState = checkBoxState;
    }
}


MainActivity.java 파일에서 구현할 코드는 다음과 같다.


 안드로이드 운영 체제에 기본 탑재된 데이터베이스

출처: http://cocomo.tistory.com/409 [Cocomo Coding]
구글 안드로이드 운영 체제에 기본 탑재된 데이터베이스

출처: http://cocomo.tistory.com/409 [Cocomo Coding]

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    SQLiteDBHandler sqLiteDBHandler;
    public SharedPreferences settings;
    ProgressDialog mProgressDialog;

    TextView textView;
    Context context;

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

        textView = (TextView) findViewById(R.id.sqlite_txt);

        sqLiteDBHandler = new SQLiteDBHandler(MainActivity.this);
        // 서버에서 데이터를 가져와서 DB에 insert
        settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);
        Uri.Builder builder = new Uri.Builder()
                .appendQueryParameter("idx", settings.getString("idx",""));
        String postParams = builder.build().getEncodedQuery();
        new getOrgChartData().execute(Value.IPADDRESS + "/get_json.php",postParams);

        // 저장버튼을 클릭하면 SQLite DB에 저장된 데이터를 폰의 주소록에 저장하는 기능
        Button btn_contactSave = (Button) findViewById(R.id.btn_sqlite_save);
        btn_contactSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<Address_Item> contacts = sqLiteDBHandler.getAllPBook();
                int num = 0;
                for(Address_Item item : contacts){
                    num++;
                    int rawContactId = 0;
                    Contacts phonebook = new Contacts(); // 전화번호부 객체 생성
                    ContentResolver cr = getContentResolver();
                    String strContactName = item.getName();
                    String strMobileNO = item.getMobileNO();
                    String strofficeNO = item.getOfficeNO();
                    String strEmail = "";
                    String strPhoto = "";
                    if (item.getProfile_image().length() > 0) {
                        strPhoto = Value.IPADDRESS + "/photos/" + item.getProfile_image();
                    }
                    rawContactId = phonebook.ContactsIDExistCheck(cr, strContactName);
                    if (rawContactId > 0) {
                        // 기존 전화번호가 존재하면 삭제하고 새로 입력
                        phonebook.ContactsIDdelete(cr, context, rawContactId);
                    }
                    phonebook.ContactsIDinsert(cr, context, strContactName, strMobileNO, strofficeNO, strEmail, strPhoto);
                }
                Toast.makeText(getApplicationContext(), "총" + num + "개 연락처가 저장되었습니다.", Toast.LENGTH_LONG).show();
            }
        });

    }

    class getOrgChartData extends AsyncTask<String, Void, String> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // Create a progressdialog
            mProgressDialog = new ProgressDialog(MainActivity.this);
            mProgressDialog.setTitle("Personal Profile JSON Parse");
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setIndeterminate(false);
            mProgressDialog.show();  // Show progressdialog
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                return PHPComm.getJson(params[0],params[1]);
            } catch (Exception e) {
                return new String("Exception: " + e.getMessage());
            }
        }

        protected void onPostExecute(String result){
            searchJSON=result;
            showJSONObjectList();
            mProgressDialog.dismiss();
        }
    }

    // 서버 정보를 파싱하기 위한 변수 선언
    String searchJSON;
    private static final String TAG_RESULTS="result";
    private static final String TAG_UID = "uid"; // 서버 테이블의 실제 필드명
    private static final String TAG_NAME = "userNM";
    private static final String TAG_MobileNO ="mobileNO";
    private static final String TAG_OfficeNO ="telNO";
    private static final String TAG_Image = "photo"; // 이미지 필드
    JSONArray peoples = null;

    protected void showJSONObjectList() {
        try {
            JSONObject jsonObj = new JSONObject(searchJSON);
            peoples = jsonObj.getJSONArray(TAG_RESULTS);

            for(int i=0;i<peoples.length();i++){
                JSONObject c = peoples.getJSONObject(i);
                final String uid = c.getString(TAG_UID);
                final String name = c.getString(TAG_NAME);
                final String mobileNO = c.getString(TAG_MobileNO);
                final String officeNO = c.getString(TAG_OfficeNO);
                final String Profile_Image = c.getString(TAG_Image);

                // 서버에서 가져온 데이터 저장
                sqLiteDBHandler.InsertData(uid,name, mobileNO, officeNO,Profile_Image);
            }

        } catch (JSONException e) {
            e.printStackTrace();
        }

        textView.setText(String.valueOf(sqLiteDBHandler.getTableRowCount()));
    }

}


도움되셨다면 00 클릭~ 해주시길 ^^


참고하면 도움이 될 게시글

https://sites.google.com/site/ydhanslab/andeuloideu/oebusqlitedbneohgi


블로그 이미지

Link2Me

,
728x90

안드로이드 어플에서 userID, userPW 정보를 자동 저장해두고 다음부터는 편리하게 접속하는 기능이 뭘까?


이런 자동로그인 처리를 가능하게 해주는 것이 Preference 이다.

간단하게 내 폰 어딘가에 userID, userPW, 자동로그인 체크 정보를 기록하는 것이다.

Preference 저장 장소 : data/data/패키지명/Shared_prefs 라는 곳에 키 - 값 쌍으로 저장된다.

Shared Preference 는 데이터의 저장보다는 UI 상태를 저장하는데 사용된다.

Shared Preference는 데이터의 저장보다는 UI 상태를 저장하는데 사용

출처: http://androidhuman.com/193 [커니의 안드로이드 이야기]


저장하기

 SharedPreferences settings = getSharedPreferences("PrefName", MODE_PRIVATE);
 SharedPreferences.Editor editor =
settings.edit();
 editor.putString(KEY_MY_PREFERENCE, text);
 editor.commit();

 불러오기

 SharedPreferences settings = getSharedPreferences("PrefName", MODE_PRIVATE);
 String text =
settings.getString(KEY_MY_PREFERENCE, "");


getSharedPreferences("settings", Activity.MODE_PRIVATE); // MODE_PRIVATE : 읽기 쓰기 가능

getSharedPreferences("settings", 0); 으로 해도 같은 의미이다.



Preference 정보 저장하는 방법


public SharedPreferences settings; // 정의

String loginID;
String loginPW;
CheckBox autologin;
Boolean loginChecked;
EditText etId;
EditText etPw;

etId = (EditText) findViewById(R.id.login_id_edit);
etPw = (EditText) findViewById(R.id.login_pw_edit);   
autologin = (CheckBox) findViewById(R.id.autologinchk);

loginID = etId.getText().toString().trim();
loginPW = etPw.getText().toString().trim();

public void onStop(){
    // 어플리케이션이 화면에서 사라질때
    super.onStop();       
    // 자동 로그인이 체크되어 있고, 로그인에 성공했으면 폰에 자동로그인 정보 저장     
    if (autologin.isChecked()) {
        settings = getSharedPreferences("settings",Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
       
         editor.putString("loginID", loginID);
         editor.putString("loginPW", loginPW);
         editor.putBoolean("LoginChecked", true);
        
         editor.commit();
    } else {
        // 자동 로그인 체크가 해제되면 폰에 저장된 정보 모두 삭제
        settings = getSharedPreferences("settings",    Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.clear(); // 모든 정보 해제
        editor.commit();
    }
}


Preference 정보 가져오는 방법


public SharedPreferences settings;
String loginID;
String loginPW;
CheckBox autologin;
Boolean loginChecked;
EditText etId;
EditText etPw;

etId = (EditText) findViewById(R.id.login_id_edit);
etPw = (EditText) findViewById(R.id.login_pw_edit);   
autologin = (CheckBox) findViewById(R.id.autologinchk);

loginID = etId.getText().toString().trim();
loginPW = etPw.getText().toString().trim();

settings = getSharedPreferences("settings", Activity.MODE_PRIVATE);

// 자동 로그인을 설정했다면 앱이 다시 시작하는 경우에도 입력했던 값이 유지되도록 하기 위한 코드

loginChecked = settings.getBoolean("LoginChecked", false);
if (loginChecked) {
    etId.setText(settings.getString("loginID", ""));
    etPw.setText(settings.getString("loginPW", ""));
    autologin.setChecked(true);
}



참고하면 도움이 될 내용

http://javaexpert.tistory.com/620

블로그 이미지

Link2Me

,