[android] SimpleCursorAdapter를 대체하기

ListView 와 연동되는 Adapter 는 다양하게 구성할 수 있지만 실무에서는 REST API 를 통한 JSON 연동이나 SQLite 와 연동하는 경우가 매우 흔합니다. 이번 포스팅에서는 Cursor 와 연동하는 경우를 간단히 처리할 수 있는 AdapterCursor 를 만들어봅니다.
 
 
 

SimpleCursorAdapter보다 더 심플하게!

우선 BaseAdapter를 근간으로 뼈대를 잡아보죠. 무엇이 우리를 귀찮게 하는지와 무엇을 구상해야 하는지 생각해보기엔 좋은 시작점입니다.

//커서를 가져온다.
final Cursor c = getQuery()

BaseAdapter adapter = new BaseAdapter(){

	@Override
	public int getCount(){return c == null ? 0 : c.getCount();}
	@Override
	public Object getItem( int i ){return null;}
	@Override
	public long getItemId( int i ){return i;}

	@Override
	public View getView( int idx, View view, ViewGroup viewGroup ){
		if( view == null ){
			//1.레이아웃로딩----------
		}

		c.moveToPosition(idx); //커서를 그 위치로

		//2.data처리-----------

		return view;
	}
};

특별한 기능을 안쓴다고 봤을 때 이 정도로 생각됩니다. 이 중에 특히 중요한 1번과 2번을 잘 처리하는 것이고 그 외에는 크게 문제는 없는 듯합니다.
 
 
 

기본 추상클래스 구성

getItem이랑 getItemId는 필요할 때만 구상하게 하고 getView처리를 정리하면 다음과 같은 추상클래스를 생각해볼 수 있습니다.

abstract class AdapterCursor extends BaseAdapter{

	private Cursor cursor;

	public AdapterCursor( Cursor $cursor ){
		cursor = $cursor;
	}

	@Override
	public View getView( int idx, View view, ViewGroup viewGroup ){
		if( view == null ){
			//1.레이아웃로딩----------
		}

		cursor.moveToPosition(idx); //커서를 그 위치로

		//2.data처리-----------

		return view;
	}

	@Override
	public int getCount(){return _c == null ? 0 : _c.getCount();}
	@Override
	public Object getItem( int $i ){return null;}
	@Override
	public long getItemId( int $i ){return $i;}
	@Override
}

실무환경에 따라 다르겠지만 이 정도를 구성해두면 제 경우는 손델 일이 크게 없었습니다. 추상클래스니 필요하면 오버라이드해서 사정에 맞게 getCount, getItem 등을 구현하시면 될 듯합니다.
 
 
 

템플릿메서드 후크

이제 getView에서 주석으로 걸려있던 두 개의 후크를 구성해보죠. 레이아웃용 후크는 View를 반환하면 될테고, data처리는 사실 반환한 것도 없습니다. 구성의 용이성을 위해 커서를 포함해서 전부 인자로 던집니다.

public abstract View getRowView();

public abstract void setRow( Cursor cursor, int idx, View view, ViewGroup viewGroup );

@Override
public View getView( int idx, View view, ViewGroup viewGroup ){
	if( view == null ) view = getRowView();

	cursor.moveToPosition(idx);

	setRow( cursor, idx, view, viewGroup );

	return view;
}

getRowView, setRow 두개의 후크로 템플릿메서드가 완성되었으므로 이제 커서용 어뎁터를 매우 간단히 구상할 수 있게 되었습니다.

Cursor cursor = getQuery("select title, userid, regdate from bbs");

AdapterCursor adapter = new AdapterCursor(cursor){

	@Override
	public View getRowView(){
		return getLayoutInflater().inflate( R.layout.row );
	}

	@Override
	public void setRow( Cursor cursor, int idx, View view, ViewGroup viewGroup ){
		( (TextView)view.findViewById(R.id.title) ).setText( cursor.getString(0) );
		( (TextView)view.findViewById(R.id.userid) ).setText( cursor.getString(1) );
		( (TextView)view.findViewById(R.id.regdate) ).setText( cursor.getString(2) );
	}
};

listView.setAdapter( adapter );

 
 
 

업데이트의 문제

커서에 의존하는 어뎁터다보니 쿼리의 결과를 뷰에 반영해줘야합니다.
기본적인 어뎁터의 업데이트 메서드는 notifyDataSetChanged() 입니다만 이 경우 커서 자체가 새로운 객체이므로 소용없습니다.
따라서 다음과 같은 업데이트 메서드를 생각해보죠.

public void update( Cursor $cursor ){
	cursor = $cursor;
	notifyDataSetChanged();
}

커서 갱신과 동시에 업데이트를 하는 방식입니다. 이렇게 되면 호스트 코드를 더욱 효율적으로 나눌 수 있게 됩니다.

//필드에 adapter를 잡아둔다
AdapterCursor adapter = null;

void setList( String $query ){
	Cursor c = getQuery($query);

	if( adapter == null ){ //어뎁터가 없으면 만든다

		adapter = new AdapterCursor(cursor){
			@Override
			public View getRowView(){
				return getLayoutInflater().inflate( R.layout.row );
			}
			@Override
			public void setRow( Cursor cursor, int idx, View view, ViewGroup viewGroup ){
				( (TextView)view.find.. ).setText( cursor.getString(0) );
				( (TextView)view.find.. ).setText( cursor.getString(1) );
				( (TextView)view.find.. ).setText( cursor.getString(2) );
			}
		};

		listView.setAdapter( adapter );

	}else{ //이미 있으면 커서를 보내 갱신

		adapter.update(c);

	}
}

 
 
 

결론

Adapter작업이 지루하고 귀찮기 때문에 이러한 형태로 다양한 추상 클래스군을 만들어두고 사용하고 있습니다. 기회가 나는대로 다른 녀석들도 공개해보죠.