-
-
Notifications
You must be signed in to change notification settings - Fork 45
[Draft] Added snackbar to show success / failure of network data #3459
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,9 +6,14 @@ import android.view.View | |||||
| import android.view.ViewGroup | ||||||
| import android.widget.FrameLayout | ||||||
| import androidx.fragment.app.Fragment | ||||||
| import androidx.lifecycle.Observer | ||||||
| import androidx.viewbinding.ViewBinding | ||||||
| import org.commcare.dalvik.R | ||||||
| import org.commcare.dalvik.databinding.LoadingBinding | ||||||
| import org.commcare.fragments.connect.base.BaseNetworkStateViewModel | ||||||
| import org.commcare.fragments.connect.base.BaseNetworkStateViewModel.NetworkState.Loading | ||||||
| import org.commcare.interfaces.base.BaseConnectView | ||||||
| import org.commcare.utils.ViewUtils | ||||||
|
|
||||||
| abstract class BaseConnectFragment<B : ViewBinding> : | ||||||
| Fragment(), | ||||||
|
|
@@ -19,6 +24,8 @@ abstract class BaseConnectFragment<B : ViewBinding> : | |||||
| private lateinit var loadingBinding: LoadingBinding | ||||||
| private lateinit var rootView: View | ||||||
|
|
||||||
| protected var baseNetworkStateViewModel: BaseNetworkStateViewModel? = null | ||||||
|
|
||||||
| /** | ||||||
| * Implement this method in child fragments to inflate their specific binding. | ||||||
| */ | ||||||
|
|
@@ -75,4 +82,52 @@ abstract class BaseConnectFragment<B : ViewBinding> : | |||||
| override fun hideLoading() { | ||||||
| loadingBinding.root.visibility = View.GONE | ||||||
| } | ||||||
|
|
||||||
| fun startListeningToNetworkState() { | ||||||
| baseNetworkStateViewModel?.networkState?.observe( | ||||||
| getViewLifecycleOwner(), | ||||||
| Observer { networkState: BaseNetworkStateViewModel.NetworkState -> | ||||||
| when (networkState) { | ||||||
| is BaseNetworkStateViewModel.NetworkState.Error -> { | ||||||
| showNetworkFailureSnackBar() | ||||||
| } | ||||||
| Loading -> { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional nit: can add
Suggested change
|
||||||
| } | ||||||
| is BaseNetworkStateViewModel.NetworkState.Success -> { | ||||||
| if (networkState.state) { | ||||||
| showNetworkSuccessSnackBar() | ||||||
| } else { | ||||||
| showNetworkFailureSnackBar() | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| }, | ||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| fun showNetworkSuccessSnackBar() { | ||||||
| ViewUtils.showSnackBarWith( | ||||||
| getString(R.string.refresh), | ||||||
| view, | ||||||
| getString(R.string.new_network_data_success), | ||||||
| View.OnClickListener { v: View? -> | ||||||
| refreshUiWithNetworkData() | ||||||
| }, | ||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| fun showNetworkFailureSnackBar() { | ||||||
| ViewUtils.showSnackBarWith( | ||||||
| getString(R.string.ok), | ||||||
| view, | ||||||
| getString(R.string.new_network_data_failure), | ||||||
| View.OnClickListener { v: View? -> | ||||||
| showOldDataOnUi() | ||||||
| }, | ||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| open fun refreshUiWithNetworkData() {} | ||||||
|
|
||||||
| open fun showOldDataOnUi() {} | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import androidx.fragment.app.Fragment; | ||
| import androidx.fragment.app.FragmentManager; | ||
| import androidx.lifecycle.Lifecycle; | ||
| import androidx.lifecycle.ViewModelProvider; | ||
| import androidx.navigation.Navigation; | ||
| import androidx.viewpager2.adapter.FragmentStateAdapter; | ||
| import androidx.viewpager2.widget.ViewPager2; | ||
|
|
@@ -58,6 +59,7 @@ public static ConnectDeliveryProgressFragment newInstance() { | |
| initialTabPosition = getArguments().getInt(TAB_POSITION, TAB_PROGRESS); | ||
| } | ||
|
|
||
| setUpViewModel(); | ||
| setupTabViewPager(); | ||
| setupJobCard(job); | ||
| setupRefreshAndConfirmationActions(); | ||
|
|
@@ -114,17 +116,28 @@ public void onTabReselected(TabLayout.Tab tab) { | |
| }); | ||
| } | ||
|
|
||
| private void setUpViewModel() { | ||
| setBaseNetworkStateViewModel(new ViewModelProvider(this).get(ConnectDeliveryProgressFragmentViewModel.class)); | ||
| startListeningToNetworkState(); | ||
| } | ||
|
|
||
| @Override | ||
| public void refresh() { | ||
| public void refreshUiWithNetworkData() { | ||
| updateLastUpdatedText(new Date()); | ||
| updateCardMessage(); | ||
| updatePaymentConfirmationTile(false); | ||
| viewPagerAdapter.refresh(); | ||
| } | ||
|
|
||
| public void fetchDeliveryProgress() { | ||
| setWaitDialogEnabled(false); | ||
| ConnectJobHelper.INSTANCE.updateDeliveryProgress(getContext(), job, true, this, success -> { | ||
| if (success) { | ||
| updateLastUpdatedText(new Date()); | ||
| updateCardMessage(); | ||
| updatePaymentConfirmationTile(false); | ||
| viewPagerAdapter.refresh(); | ||
| } | ||
| }); | ||
| ((ConnectDeliveryProgressFragmentViewModel)getBaseNetworkStateViewModel()).fetchData(requireContext(), job, this); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Might be nice to have a getViewModel helper to handle retrieving the base view model and casting it, since that code is a bit clunky
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @OrangeAndGreen We might have to handle the same way as ViewBinding but as many other fragments are using the |
||
| } | ||
|
|
||
| @Override | ||
| public void refresh() { | ||
| ((ConnectDeliveryProgressFragmentViewModel)getBaseNetworkStateViewModel()).setShowLoading(true); | ||
| fetchDeliveryProgress(); | ||
| } | ||
|
|
||
| private void setWaitDialogEnabled(boolean enabled) { | ||
|
|
@@ -176,7 +189,7 @@ private void setupJobCard(ConnectJobRecord job) { | |
| public void onResume() { | ||
| super.onResume(); | ||
| if (PersonalIdManager.getInstance().isloggedIn()) { | ||
| refresh(); | ||
| fetchDeliveryProgress(); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package org.commcare.fragments.connect | ||
|
|
||
| import android.content.Context | ||
| import org.commcare.android.database.connect.models.ConnectJobRecord | ||
| import org.commcare.connect.ConnectActivityCompleteListener | ||
| import org.commcare.connect.ConnectJobHelper | ||
| import org.commcare.fragments.connect.base.BaseNetworkStateViewModel | ||
| import org.commcare.interfaces.base.BaseConnectView | ||
| import java.util.Date | ||
|
|
||
| class ConnectDeliveryProgressFragmentViewModel : BaseNetworkStateViewModel() { | ||
| var previousLastUpdatedOn: Date? = null | ||
| var showLoading = false // by default should be false, only true whenever user presses sync button | ||
|
|
||
| fun fetchData( | ||
| context: Context, | ||
| job: ConnectJobRecord, | ||
| view: BaseConnectView, | ||
| ) { | ||
| // TODO | ||
| // lastModifiedOn = job.lastUpdate // this parameter is not coming currently from server. | ||
| launchTask { | ||
| ConnectJobHelper.updateDeliveryProgress( | ||
| context, | ||
| job, | ||
| showLoading, | ||
| view, | ||
| object : ConnectActivityCompleteListener { | ||
| override fun connectActivityComplete(success: Boolean) { | ||
| showLoading = false // back to default value | ||
| // TODO | ||
| // Real check is `success && job.lastUpdate>previousLastUpdatedOn` | ||
| // to be used whenever server starts sending lastModifiedOn | ||
| if (success) { | ||
| networkStateLiveData.value = NetworkState.Success(success) | ||
| } else { | ||
| networkStateLiveData.value = NetworkState.Error(Exception("UNKNOWN_EXCEPTION")) | ||
| } | ||
| } | ||
| }, | ||
| ) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package org.commcare.fragments.connect.base | ||
|
|
||
| import androidx.lifecycle.LiveData | ||
| import androidx.lifecycle.MutableLiveData | ||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import kotlinx.coroutines.CoroutineExceptionHandler | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| open class BaseNetworkStateViewModel : ViewModel() { | ||
| sealed class NetworkState { | ||
| object Loading : NetworkState() | ||
|
|
||
| data class Success( | ||
| val state: Boolean, | ||
| ) : NetworkState() | ||
|
|
||
| data class Error( | ||
| val exception: Exception, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the coderabbit comment in this file: Given the CoroutineExceptionHandler provides a Throwable instead of Exception, any reason not to store it as a Throwable here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done here |
||
| ) : NetworkState() | ||
| } | ||
|
|
||
| protected val networkStateLiveData = MutableLiveData<NetworkState>() | ||
| val networkState: LiveData<NetworkState> = networkStateLiveData | ||
|
|
||
| protected val handler = | ||
| CoroutineExceptionHandler { _, exception -> | ||
| networkStateLiveData.value = NetworkState.Error(if (exception is Exception) exception else Exception(exception)) | ||
| } | ||
|
|
||
| protected fun launchTask(task: suspend () -> Unit) { | ||
| viewModelScope.launch(handler) { | ||
| networkStateLiveData.value = NetworkState.Loading | ||
| task() | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,21 +8,22 @@ | |||||
|
|
||||||
| public class ViewUtils { | ||||||
| /** | ||||||
| * Displays a SnackBar with the given message and an "OK" button. | ||||||
| * The SnackBar will remain visible indefinitely until the "OK" button is pressed. | ||||||
| * Displays a SnackBar with the given message and an button with provided text. | ||||||
| * The SnackBar will remain visible indefinitely until the button is pressed. | ||||||
| * | ||||||
| * @param btnText Button text | ||||||
| * @param view The view to find a parent from. This view is used to create the SnackBar. | ||||||
| * @param message The message text to show in the SnackBar. | ||||||
| * @param okClickListener The callback to be invoked when the "OK" button is clicked. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Javadoc references hardcoded "OK" button. The - * @param okClickListener The callback to be invoked when the "OK" button is clicked.
+ * @param okClickListener The callback to be invoked when the action button is clicked.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| */ | ||||||
| public static void showSnackBarWithOk(View view, String message, View.OnClickListener okClickListener) { | ||||||
| public static void showSnackBarWith(String btnText, View view, String message, View.OnClickListener okClickListener) { | ||||||
| Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE) | ||||||
| .setAction("OK", okClickListener) | ||||||
| .setAction(btnText, okClickListener) | ||||||
| .show(); | ||||||
| } | ||||||
|
Comment on lines
10
to
23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we refactor this function (and its comment) to not reference "ok" for the click listener (i.e. |
||||||
| public static void showSnackBarWithDismissAction(View view, String message) { | ||||||
| public static void showSnackBarWithDismissAction(String btnText,View view, String message) { | ||||||
| Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE); | ||||||
| snackbar.setAction(view.getContext().getString(R.string.ok),v -> snackbar.dismiss()); | ||||||
| snackbar.setAction(btnText,v -> snackbar.dismiss()); | ||||||
| snackbar.show(); | ||||||
| } | ||||||
| } | ||||||
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 these messages are good, but I'd love it if we could get some input from the product team (i.e. Kim, Mary, Ashish, or Brendon) if not already