-
Notifications
You must be signed in to change notification settings - Fork 0
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
Conversation
|
||
import android.os.Parcel | ||
import android.os.Parcelable | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy-paste leftover :)
There was a problem hiding this comment.
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" /> |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely done! :)
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