Skip to content

Contacts implementation #107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 9, 2021
Merged

Contacts implementation #107

merged 9 commits into from
Nov 9, 2021

Conversation

tomas-kovacs
Copy link
Contributor

@tomas-kovacs tomas-kovacs commented Nov 5, 2021

One Line Summary

Get and display the user's contacts

Issue(s) Addressed

#32

Steps to Reproduce/Test

From the First Screen, tap on the three buttons menu and select Contacts to see the list of contacts (read contacts permission will be requested).
Tap on a contact from the list and a bottom sheet dialog will be displayed with the contact's name and phone number.

Checklist

  • Functionally tested
  • Unit tests created / updated
  • Backend dependencies deployed (if needed)


import android.os.Parcel
import android.os.Parcelable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are u implementing Parcebla by hand instead of using just the @Serializable annotation for it for any reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parcelable is better than Serializable because it creates less temporary objects. I removed the unnecessary code with the @Parcelize annotation.

import com.rocketinsights.android.viewmodels.PermissionsViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class ContactsFragment : Fragment(R.layout.fragment_contacts), LoaderManager.LoaderCallbacks<Cursor> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the BaseFragment and override the life-cycle methods as needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

override fun onDestroyView() {
super.onDestroyView()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is recommended that any triggered action in a life-cycle event that corresponds to the destruction of the component (like in this case "onDestroyView") be put before the super, to ensure that it is called even when the component is still "alive". So in this case please assign the _biding to null before calling the super.onDestroyView.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are totally right. I fixed it but I'm going to remove this base class and start using the viewBinding extension functions as Ivan suggested.

Copy link
Contributor

@IvanToplak IvanToplak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed it and tested it. I've left some suggestions for improvements.
It runs on Samsung device (Android 11), but not on Xiaomi (Android 10).
On Xiaomi I get this exception:

E/AndroidRuntime: FATAL EXCEPTION: ModernAsyncTask #1
    Process: com.rocketinsights.android.dev, PID: 12910
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at androidx.loader.content.ModernAsyncTask$3.done(ModernAsyncTask.java:164)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
        at java.util.concurrent.FutureTask.run(FutureTask.java:271)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
     Caused by: android.database.sqlite.SQLiteException: no such column: true (code 1 SQLITE_ERROR): , while compiling: SELECT _id, display_name, data1 FROM view_data data LEFT OUTER JOIN (SELECT 0 as STAT_DATA_ID,0 as x_times_used, 0 as x_last_time_used,0 as times_used, 0 as last_time_used where 0) as data_usage_stat ON (STAT_DATA_ID=data._id) WHERE (1 AND mimetype_id=1 AND (1=1)) AND (((display_name NOTNULL) AND (display_name != '') AND (has_phone_number = true) AND (mimetype = 'vnd.android.cursor.item/phone_v2') AND (raw_contact_id = name_raw_contact_id))) ORDER BY display_name ASC
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:184)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
        at android.content.ContentProviderProxy.query(ContentProviderNative.java:423)
        at android.content.ContentResolver.query(ContentResolver.java:946)
        at android.content.ContentResolver.query(ContentResolver.java:881)
        at androidx.core.content.ContentResolverCompat.query(ContentResolverCompat.java:81)
        at androidx.loader.content.CursorLoader.loadInBackground(CursorLoader.java:63)
        at androidx.loader.content.CursorLoader.loadInBackground(CursorLoader.java:41)
        at androidx.loader.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:307)
        at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:60)
        at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:48)
        at androidx.loader.content.ModernAsyncTask$2.call(ModernAsyncTask.java:141)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at java.lang.Thread.run(Thread.java:919) 
I/Process: Sending signal. PID: 12910 SIG: 9

I've also noticed that app doesn't ask for permission if it was denied before.

import android.os.Parcel
import android.os.Parcelable

data class Contact(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest usage of @Parcelize annotation to reduce the boilerplate. More about it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

abstract class BaseBottomSheetFragment<B : ViewBinding> : BottomSheetDialogFragment() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have ViewBindingExt.kt that can be used on Fragments to reduce the boilerplate. Could you try to use it instead of this base class?

private val _contactsLiveData: MutableLiveData<List<Contact>> by lazy {
MutableLiveData<List<Contact>>()
}
val contactsLiveData: LiveData<List<Contact>> = _contactsLiveData
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest implementing get() for this backing property to avoid generation of the backing field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

android:layout_height="match_parent"
android:layout_margin="@dimen/margin" />

<androidx.core.widget.ContentLoadingProgressBar
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also layout_progress.xml that can be used here so that we don't repeat ContentLoadingProgressBar on every screen that requires it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

<fragment
android:id="@+id/contacts_fragment"
android:name="com.rocketinsights.android.ui.ContactsFragment"
android:label="@string/contacts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency it would be nice to put contacts_screen_title here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import com.rocketinsights.android.viewmodels.PermissionsViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class ContactsFragment : Fragment(R.layout.fragment_contacts), LoaderManager.LoaderCallbacks<Cursor> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use BaseFragment here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:fitsSystemWindows="true"
tools:context=".ui.MessagesFragment">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy-paste leftover :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch ;)

android:id="@+id/recycler_view_contacts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would look better without this margin. Maybe use some horizontal padding with android:clipToPadding="false"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


fun getContactsQueryId(): Int = CONTACTS_QUERY_ID

companion object {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, but I think that we were defining the companion object at the top of the class instead of at the bottom.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, moved up


private val binding by viewBinding(FragmentContactsBinding::bind)
private val viewModel by viewModel<ContactsViewModel>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried updating Koin to 3.1.3 and using this new feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great new feature. I updated Koin version to 3.1.3 to support this

Copy link
Contributor

@IvanToplak IvanToplak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done! :)

@tomas-kovacs tomas-kovacs merged commit c0b39d7 into master Nov 9, 2021
@tomas-kovacs tomas-kovacs deleted the feature/contacts branch November 9, 2021 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants