Contact Provider using View Model and Live Data

Viewed 5664

My android app is using contacts providers to display all the contacts to the user. I'm using Loaders to load the contacts by following the tutorial/documentation at https://developer.android.com/training/contacts-provider/retrieve-names

But from the link https://developer.android.com/guide/components/loaders, it is mentioned that loaders are deprecated as of Android P.

Loaders have been deprecated as of Android P (API 28). The recommended option for dealing with loading data while handling the Activity and Fragment lifecycles is to use a combination of ViewModels and LiveData. ViewModels survive configuration changes like Loaders but with less boilerplate. LiveData provides a lifecycle-aware way of loading data that you can reuse in multiple ViewModels. You can also combine LiveData using MediatorLiveData, and any observable queries, such as those from a Room database, can be used to observe changes to the data. ViewModels and LiveData are also available in situations where you do not have access to the LoaderManager, such as in a Service. Using the two in tandem provides an easy way to access the data your app needs without having to deal with the UI lifecycle. To learn more about LiveData see the LiveData guide and to learn more about ViewModels see the ViewModel guide.

So my question is:

1. How can we fetch the contacts using android view Model and live data from contact providers?
2. Can we use Room database for contact providers?

Below you can find the link to the source code where I tried to use the Android View Model and Live data to fetch the contacts from ContactProviders.

https://github.com/deepak786/phonebook-contacts
3. What can be improved in the above source code so that fetching will be faster?

Thanks & Regards
Deepak

2 Answers

Below you can find a very simple solution for loading contacts using MVVM:

https://github.com/NaarGes/Android-Contact-List

Here comes a bit of code in case the link is no longer working.

First, let's create a simple POJO for contacts UserObject.java

public class UserObject {

    private String email, name, phone;

    public UserObject() {
        // EMPTY CONSTRUCTOR FOR FIREBASE REALTIME DATABASE
    }

    public UserObject(String email, String name, String phone) {
        this.email = email;
        this.name = name;
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

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

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

Now, let's create our repository ContactRepository.java

public class ContactRepository {

    private Context context;

    private static final String TAG = "debinf ContRepo";

    public ContactRepository(Context context) {
        this.context = context;
    }

    public List<UserObject> fetchContacts() {
        List<UserObject> contacts = new ArrayList<>();

        Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        Log.i(TAG, "fetchContacts: cursor.getCount() is "+cursor.getCount());
        if ((cursor != null ? cursor.getCount() : 0) > 0) {
            while (cursor.moveToNext()) {

                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                UserObject contact = new UserObject("",name, phone);

                Log.i(TAG, "fetchContacts: phone is "+phone);

                contacts.add(contact);
            }
        }
        if (cursor != null) {
            cursor.close();
        }
        return contacts;
    }
}

Next, we create our ContactViewModel.java


public class ContactViewModel extends ViewModel {

    private ContactRepository repository;
    private MutableLiveData<List<UserObject>> contacts;

    public ContactViewModel(Context context) {
        repository = new ContactRepository(context);
        contacts = new MutableLiveData<>();
    }

    public MutableLiveData<List<UserObject>> getContacts() {
        contacts.setValue(repository.fetchContacts());
        return contacts;
    }
}

Next, we create a factory for our ViewModel ContactViewModelFactory.java

public class ContactViewModelFactory implements ViewModelProvider.Factory {

    private Context context;

    public ContactViewModelFactory(Context context) {
        this.context = context;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (modelClass.isAssignableFrom(ContactViewModel.class)) {
            return (T) new ContactViewModel(context);
        }
        throw new IllegalArgumentException("Unknown ViewModel class");
    }
}

Let's not forget to add permission in our AndroidManifest

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

And finally, we ask for permission in our MainActivity.java

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS,Manifest.permission.READ_CONTACTS,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
}

and bring our contacts to surface

ContactViewModelFactory factory = new ContactViewModelFactory(this);
viewModel = ViewModelProviders.of(this, factory).get(ContactViewModel.class);

viewModel.getContacts().observe(this, new Observer<List<UserObject>>() {
    @Override
    public void onChanged(@Nullable List<UserObject> userObjects) {
        Log.i(TAG, "ViewModel: userObjects size is "+userObjects.size());
        Log.i(TAG, "ViewModel: userObjects size is "+userObjects.get(1).getPhone());
    }
});
class ContactsViewModel(private val contentResolver: ContentResolver) : ViewModel() 
{

    lateinit var contactsList: LiveData<PagedList<Contact>>

    fun loadContacts() {
        val config = PagedList.Config.Builder()
                .setPageSize(20)
                .setEnablePlaceholders(false)
                .build()
        contactsList = LivePagedListBuilder<Int, Contact>(
                ContactsDataSourceFactory(contentResolver), config).build()
    }
}

class ContactsDataSourceFactory(private val contentResolver: ContentResolver) :
        DataSource.Factory<Int, Contact>() {

    override fun create(): DataSource<Int, Contact> {
        return ContactsDataSource(contentResolver)
    }
}

class ContactsDataSource(private val contentResolver: ContentResolver) :
        PositionalDataSource<Contact>() {

    companion object {
        private val PROJECTION = arrayOf(
                ContactsContract.Contacts._ID,
                ContactsContract.Contacts.LOOKUP_KEY,
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        )
    }

    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Contact>) {
        callback.onResult(getContacts(params.requestedLoadSize, params.requestedStartPosition), 0)
    }

    override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Contact>) {
        callback.onResult(getContacts(params.loadSize, params.startPosition))
    }

    private fun getContacts(limit: Int, offset: Int): MutableList<Contact> {
        val cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
                PROJECTION,
                null,
                null,
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY +
                        " ASC LIMIT " + limit + " OFFSET " + offset)

        cursor.moveToFirst()
        val contacts: MutableList<Contact> = mutableListOf()
        while (!cursor.isAfterLast) {
            val id = cursor.getLong(cursor.getColumnIndex(PROJECTION[0]))
            val lookupKey = cursor.getString(cursor.getColumnIndex(PROJECTION[0]))
            val name = cursor.getString(cursor.getColumnIndex(PROJECTION[2]))
            contacts.add(Contact(id, lookupKey, name))
            cursor.moveToNext()
        }
        cursor.close()

        return contacts
    }
}

Please find the full source code here.

Related