도전과제 21,22 : 책 정보 저장과 조회 기능 만들기 (Do it Android 앱 프로그래밍) [JAVA]
반응형

 

도전과제 21,22

탭으로 구성된 화면을 만들고 첫 번째 탭 화면에서는 책 정보를 저장, 두 번째 탭 화면에서는 책 정보를 조회할 수 있도록 합니다.

  1. 탭 모양의 화면을 구성하고 두 개의 탭 버튼을 추가합니다.
  2. 첫 번째 탭에서는 책 정보를 저장할 수 있도록 합니다. 이 화면은 도전! 17의 미션과 같습니다(제목,저자,내용 입력 후 [저장] 버튼 누르면 데이터베이스에 저장됨.)
  3. 두 번째 탭에서는 책 정보를 조회하여 리스트로 보여줄 수 있도록 합니다. 리싸이클러뷰의 각 아이템에는 책 제목과 저자가 표시됩니다.
  4. 책 정보를 저장하거나 조회할 때 모두 데이터베이스를 사용합니다. 즉, 책 정보를 저장할 때는 데이터베이스에 저장하고 조회할 때는 데이터베이스에 저장된 데이터를 가져와서 보여줍니다.

참고할점
데이터베이스와 테이블은 미리 만들어져 있어야 합니다.
그리고 앱이 실행될 때 데이버테이스 열기가 미리 진행되어야 합니다.
책 정보를 저장할 때는 INSERT SQL문을 사용하고 책 정보를 조회할 때는 SELECT SQL 문을 사용합니다.

 

풀이

우선 화면은 아래와 같이 구성하였다. 큰 메인화면에 두 개의 프레그먼트가 존재하는데, 하나는 책을 추가하고, 하나는 책을 보여주는 역할을 한다.

메인에서 AppBarLayout을 통해 두 프래그먼트를 바꿔가며 화면에 띄우게 했다.

우선 데이터베이스를 살펴보면, DatabaseHelper 클래스를 사용하여 db를 만들고, 넣을 데이터의 형식들을 지정한 Table을 먼저 만들어야 한다. 안드로이드는 임베디드 데이터베이스로 SQLite를 가지고 있기 때문에 이에 맞춰서 문법도 사용해야 한다. 또한, DatabaseHelper에서 생성자를 통해 원하는 이름의 db를 만들어 내고, onCreate로 생성될 때 같이 테이블까지 설정할 수 있다. 

DatabaseHelper dbHelper;
SQLiteDatabase database;
String tablename = "bookrecords";
  
private void createDatabase(String name){
	println("createDatabase 호출됨.");
	dbHelper = new DatabaseHelper(this);	//DatabaseHelper 생성
	database = dbHelper.getWritableDatabase(); //SQLiteDatabase 참조
	println("데이터베이스 생성됨 : " + name);
    }

private void createTable(String name){
	println("createTable 호출됨.");
	if(database == null){
		println("데이터베이스를 먼저 생성하세요.");
		return;
	}
	database.execSQL("create table if not exists " + name + "(" + "_id integer PRIMARY KEY autoincrement, " + " name text, " + "writer text, " + "contents text)");
	println("테이블 생성함 : " + name);
}

//...

public class DatabaseHelper extends SQLiteOpenHelper {
    public static String NAME = "library.db";
    public static String TABLENAME = "bookrecords";
    public static int VERSION = 1;

    public DatabaseHelper(Context context){
        super(context,NAME,null,VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        println("onCreate 호출됨");
        String sql = "create table if not exists "+TABLENAME+"( _id integer PRIMARY KEY autoincrement, name text, writer text, contents text)";
        sqLiteDatabase.execSQL(sql);
    }

    public void onOpen(SQLiteDatabase db){
        println("onOpen 호출됨");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        println("onUpgrade 호출됨 : " +i+" -> "+ i1);
        if(i1 > 1){
            sqLiteDatabase.execSQL("DROP TABLE IF EXISTS "+TABLENAME);
        }
    }
    public void println(String data){
        Log.d("DatabaseHelper" , data);
    }
}

SQLite 역시 표준sql 문법을 사용할 수 있지만, 우리가 주로 쓰는 SQL과 다른 점으론허용되는 데이터 타입이  Null, Integer, Real(부동소수점), Text, Blob (입력 데이터 Bit그대로) 5가지 타입밖에 없는점이 큰 차이점이다.

이제 Insert와 Select문을 사용하여 데이터를 조회, 삽입하는 방법을 살펴보자. Insert는 앞서 만들어진 테이블에 형식에 맞게 데이터를 넣어주면 된다. INSERT INTO (TABLE) (COL1,COL2,COL3..) VALUES (VAL1,VAL2,VAL3) 처럼 넣으면 된다. Select는 SELECT (COL1,COL2,COL3...) FROM (TABLE)과 같은 쿼리문을 사용하면 되는데, 이 때 앞서 보았듯이 Cursor를 사용해서 결과값에 접근할 수 있다. Cursor에서 moveToPosition으로 계속 다음 행을 읽어나가면 된다.

public void exequteQuery(){
        println("exequteQuery 호출됨");
        Cursor cursor = database.rawQuery("select _id, name, writer, contents from " + tablename , null);
        int recordCount = cursor.getCount();
        println("레코드 갯수 : " + recordCount);

        for(int i=beforeposition; i< recordCount; i++){
            cursor.moveToPosition(i);
            int id = cursor.getInt(0);
            String name = cursor.getString(1);
            String writer = cursor.getString(2);
            String contents =cursor.getString(3);

            println("레코드#" + i + " : " + id + ", " + name + ", " + writer + ", " + contents);
            books.add(new Book(name,writer,contents));
        }
        beforeposition = recordCount; //여기까지 읽었다고 표시
        cursor.close();
        senddata2();
    }
    
     private void insertRecord(){
        println("insertRecod 호출됨.");
        if(database==null){
            println("데이터베이스를 먼저 생성하세요.");
            return;
        }
        if(tablename == null){
            println("테이블을 먼저 생성하세요.");
            return;
        }
        database.execSQL("insert into " + tablename + "(name, writer, contents) " + " values " + "('" + book.getName() + "' , '"+ book.getWriter() + "' , '" + book.getContents() + "')" );
        println("레코드 추가됨");
    }

이제 이 값들을 화면에 보이는 일만 남았다. 평소처럼 리사이클뷰를 사용했다면 BOOK 객체를 만들고 BookAdapter를 통해 아이템을 등록하고, UI로 출력해서 띄울 수 있었을 테지만, 약간 다른 부분이 생겼다. 리사이클러뷰를 프래그먼트로 구성한 것이다. 마찬가지로 데이터를 추가할 때도 프래그먼트에서 -> 메인액티비티로 보내야했다.

우선 프래그먼트 -> 액티비티로 객체를 전송하는 것부터 살펴보자. 우선 메소드를 통해서 데이터를 전송해야 하는데, 이 메소드를 인터페이스로 프래그먼트에 구현해놔야 한다. 그리고 프래그먼트에 MainActivity 객체를 만들어 이 인터페이스를 사용하여 데이터를 전송시킬 수 있다.

public class fragment1 extends Fragment {
    EditText editName;
    EditText editWriter;
    EditText editContents;
    private MainActivity activity;

    Book book;

    //프래그먼트 -> 액티비티로 데이터 전송
    public void onAttach(Context context) {
        super.onAttach(context);
        if(context instanceof MainActivity){
            this.activity = (MainActivity) context; //메인액티비티 얻어내기
        }
    }

    public void onDetach(Context context) {
        super.onDetach();
        activity = null;
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance){
        ViewGroup rootview = (ViewGroup) inflater.inflate(R.layout.fragment1, container, false);
        Button button = rootview.findViewById(R.id.button);

        editName = rootview.findViewById(R.id.editName);
        editWriter = rootview.findViewById(R.id.editWriter);
        editContents = rootview.findViewById(R.id.editContent);



        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String name = (editName.getText().toString());
                String writer = (editWriter.getText().toString());
                String contents = (editContents.getText().toString());
                book = new Book(name, writer,contents);
                ((sendDataListener)activity).sendata(book); //데이터 메인액티비티로 전송
            }
        });

    return rootview;
    }
    public interface sendDataListener{
        void sendata(Book book);
    }

}

이제 메인액티비티에서 이 인터페이스를 구현하여 데이터를 전송받았을 때 실행하는 메소드를 구현시키면 된다.

public class MainActivity extends AppCompatActivity implements fragment1.sendDataListener {

//.....

@Override
    public void sendata(Book book) {
        this.book = book;
        Toast.makeText(getApplicationContext(), book.getName() + " ",Toast.LENGTH_LONG).show();
        insertRecord();
    }

다음으로는 얻어온 책들을 프래그먼트의 리사이클뷰에 표시하기 위해 액티비티 -> 프래그먼트로 데이터를 전송하는 방법을 살펴보자. 액티비티 -> 프래그먼트로 데이터 전송은 Bundle을 사용하는데, 객체를 Bundle을 통해 보내기 위해선 보낼 클래스를 Parcelable (or Serializable)하게 만들어야 한다. Serializable은 자바 기본 제공 방법이고 Parcelable은 만들어진 방법이라 하는데, Parcelable이 최적화가 잘 되어있다 해서 조금 복잡하지만 이 방법을 사용했다. 객체를 Parcelable 인터페이스를 구현하게 만들고, 생성자 (Parcel에서 읽기)와 writeToParcel(Parcel로 넣기) 등을 구현해준다.

public class Book implements Parcelable {
    String name;
    String writer;
    String contents;
    
    //...

    public Book(Parcel parcel) {
        name  = parcel.readString();
        writer = parcel.readString();
        contents = parcel.readString();
    }
        @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(name);
        parcel.writeString(writer);
        parcel.writeString(contents);
    }
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel parcel) {
            return new Book(parcel);
        }

        @Override
        public Book[] newArray(int i) {
            return new Book[i];
        }
    };
}

이제 메인 액티비티에서는 데이터를 보낼 때 Bundle을 통해 Parcel화 시킨 책들의 리스트를 프래그먼트로 보낸다.

public void senddata2(){
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList("booklist",books);
        fragment2.setArguments(bundle);
    }

이제 프래그먼트에선 받아서 사용하면 된다.

 public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstance){
        //...
        
        Bundle bundle = getArguments();
        ArrayList<Book> booklist; = bundle.<Book>getParcelableArrayList("booklist");
        recyclerView.setAdapter(adapter);
        adapter.setItems(booklist);
        
        return view;
    }

추가적으로 이전 도전과제 14에서처럼 리사이클러뷰의 아이템을 터치했을 때 토스트 메시지도 뜨도록 Listener 인터페이스를 구현하여 만들었다.

 

결과

전체 소스코드 : https://github.com/howtolivelikehuman/DoitAndroid/tree/master/DoitMission_22/app/src/main

 

howtolivelikehuman/DoitAndroid

Do it Android programing (JAVA). Contribute to howtolivelikehuman/DoitAndroid development by creating an account on GitHub.

github.com

 

 

반응형