본문으로 바로가기

Cursor Adapter#4 - Loader

category 개발이야기/Android 2017. 10. 20. 13:00
반응형

Loader의 사용

Loader는 안드로이드 3.0부터 제공되는 api로 activity와 fragment의 복잡한 생명주기로 인하여startManagingCurosr()와 requery()를 대체한다.

즉, Activity와 Fragment의 변화에 따라 DB의 requery 작업을 수행하는 역할을 한다.


로더의 사용은 아래 세단계로 이루어 진다.

1. 각 Activity나 fragment에서 LoaderManager 생성

2. LoaderManager.LoaderCallabacks 구현

3. Loader의 생성 -> CursorLoader나 AsyncTaskLoader 사용

아래는 fragment에서 Loader를 이용하여 list를 만드는 예제이다.

public class FragmentLoader extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentManager fm = getFragmentManager();
        
        if (fm.findFragmentById(android.R.id.content) == null) {
            CursorLoaderListFragment list = new CursorLoaderListFragment();
            fm.beginTransaction().add(android.R.id.content, list).commit();
        }
    }

    public static class CursorLoaderListFragment extends ListFragment
            implements LoaderManager.LoaderCallbacks<Cursor> {

        SimpleCursorAdapter mAdapter;
        String mCurFilter;

        @Override 
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            setEmptyText("No phone numbers"); //빈 화면에서 보여줄 문구
            setHasOptionsMenu(true); // ActionBar를 사용하기 위해 설정

            //cursor 부분에 null을 넘겨준다.
            mAdapter = new SimpleCursorAdapter(getActivity(),
                    android.R.layout.simple_list_item_2, null,
                    new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                    new int[] { android.R.id.text1, android.R.id.text2 }, 0);
            setListAdapter(mAdapter);
            getLoaderManager().initLoader(0, null, this); // Loader init
        }

        static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            Contacts._ID,
            Contacts.DISPLAY_NAME,
            Contacts.CONTACT_STATUS,
            Contacts.CONTACT_PRESENCE,
            Contacts.PHOTO_ID,
            Contacts.LOOKUP_KEY,
        };

        @Override
		public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            Uri baseUri = Contacts.CONTENT_URI;

            String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                    + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                    + Contacts.DISPLAY_NAME + " != '' ))";
            
            //CursorLoader를 이용하여 Loader를 생성
            return new CursorLoader(getActivity(), baseUri,
                    CONTACTS_SUMMARY_PROJECTION, select, null,
                    Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        }

        @Override        
		public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            //Load가 완료되면 cursor swap를한다.
            mAdapter.swapCursor(data);
        }

        @Override
		public void onLoaderReset(Loader<Cursor> loader) {
            //Loader 사용이 완료되면 cursor 해제
            mAdapter.swapCursor(null);
        }
    }
}


initLoader

- id: loader를 구분하기 위한 id 설정 - 여러개의 contentProvider를 사용한다면 구분해야 한다.

- args: loader 생성시 추가적으로 bundle을 넘겨줄 수 있다.

- callback: LoaderCallback을 생성하여 전달한다.


LoaderManager.LoaderCallbacks

1. onCreateLoader()

initLoader()에 의해서 호출되는 method로 Loader 객체를 생성한다.

id와 args는 intiLoader()에서 넘어온다. 객체를 직접 생성하기 보다는 AsyncTaskLoader나 CursorLoader를 이용하여 생성한다.


2. onLoadFinished()

자료를 읽어 시스템 버퍼에 저장한다. 자료 읽기가 완료되면 호출된다.


3. onLoaderReset()

fragment 생명주기에 따라서 읽은 자료를 살제할때 사용한다.


Loader를 이용한 데이터 Search

사실 위 예제는 굳이 loader를 쓰지 않아도 된다. asyncQueryHandler를 사용해도 되고, 그냥 cursor adpater를 사용해도 된다.

단, 화면에서 검색기능을 이용하여, 실시간으로 list나, drop down view를 업데이트 해주는 시나리오에서는 loader를 사용하면 매우 편리하게 구현할 수 있다.


아래는 ActionBar에서 searchView를 이용하여 검색하고, 해당 결과를 화면에 실시간으로 보여주는 예제이다.


1. SearchView의 변경을 확인하기 위해 onQueryTextListener를 구현한다.

2. Loader의 구현을 위해 Loader.Callbacks를 구현한다.

public class FragmentListCursorLoader extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FragmentManager fm = getFragmentManager();
        
        if (fm.findFragmentById(android.R.id.content) == null) {
            CursorLoaderListFragment list = new CursorLoaderListFragment();
            fm.beginTransaction().add(android.R.id.content, list).commit();
        }
    }

    public static class CursorLoaderListFragment extends ListFragment
            implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

        SimpleCursorAdapter mAdapter;
        String mCurFilter;

        @Override 
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            setEmptyText("No phone numbers"); //빈화면에 출력 할 문구
            setHasOptionsMenu(true);
            
            // cursor 자리는 null로 비워둔다
            mAdapter = new SimpleCursorAdapter(getActivity(),
                    android.R.layout.simple_list_item_2, null,
                    new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                    new int[] { android.R.id.text1, android.R.id.text2 }, 0);
            setListAdapter(mAdapter);
            getLoaderManager().initLoader(0, null, this); //loader 생성 호출
        }

        @Override 
        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            //ActionBar에 search bar를 만든다.
            MenuItem item = menu.add("Search");
            item.setIcon(android.R.drawable.ic_menu_search);
            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
            SearchView sv = new SearchView(getActivity());
            sv.setOnQueryTextListener(this); //Search bar에 Query Text Listener 부착
            item.setActionView(sv);
        }

        @Override
		public boolean onQueryTextChange(String newText) {
            mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
            //Search 문구가 변경되면 restartLoader()를 호출한다.
            getLoaderManager().restartLoader(0, null, this);
            return true;
        }

        @Override
		public boolean onQueryTextSubmit(String query) {
            return true;
        }
        
        @Override 
        public void onListItemClick(ListView l, View v, int position, long id) {
            TextView tv = ((TwoLineListItem)v).getText1();
            Toast.makeText(getActivity(), tv.getText() + " Clicked", Toast.LENGTH_SHORT).show();            
            Log.i("FragmentComplexList", "Item clicked: " + id);
        }

        static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            Contacts._ID,
            Contacts.DISPLAY_NAME,
            Contacts.CONTACT_STATUS,
            Contacts.CONTACT_PRESENCE,
            Contacts.PHOTO_ID,
            Contacts.LOOKUP_KEY,
        };

        @Override
		public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            Uri baseUri;
            if (mCurFilter != null) {
                baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                        Uri.encode(mCurFilter));
            } else {
                baseUri = Contacts.CONTENT_URI;
            }

            String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                    + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                    + Contacts.DISPLAY_NAME + " != '' ))";
            //CursorLoader를 이용하여 Loader 객체 생성
            return new CursorLoader(getActivity(), baseUri,
                    CONTACTS_SUMMARY_PROJECTION, select, null,
                    Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        }

        @Override
		public void onLoadFinished(Loader<Cursor> loader, Cursor data) {            
            mAdapter.swapCursor(data);
        }

        @Override
		public void onLoaderReset(Loader<Cursor> loader) {            
            mAdapter.swapCursor(null);
        }
    }


1. restartLoader()


initLoader()와 기능은 유사하지만 initLoader()는 onCreateLoader()를 한번만 수행하고, 이후 생성된 객체가 있으면 재활용 한다. 하지만 search의 경우 매번 query하여 검색해야 하므로 onCreateLoader()를 매번 호출해야한다. 따라서 restartLoader()를 사용하여 매번 onCreateLoader()를 호출하도록 한다
사실 Loader는 search와 같이 사용되는게 대부분이므로 android 사이트에서도 이를 예제로 보여주고 있다. 동일한 예제지만 아래의 페이지를 참고하여 샘플을 확인해 보는걸 추천한다
https://developer.android.com/reference/android/app/LoaderManager.html


반응형