How to Implement a Progress Bar Using the Mvvm Pattern

How to implement a progress bar using the MVVM pattern

Typically your UI would simply bind to properties in your VM:

<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}" 
Visibility="{Binding ProgressVisibility}"/>

Your VM would use a BackgroundWorker to do the work on a background thread, and to periodically update the CurrentProgress value. Something like this:

public class MyViewModel : ViewModel
{
private readonly BackgroundWorker worker;
private readonly ICommand instigateWorkCommand;
private int currentProgress;

public MyViewModel()
{
this.instigateWorkCommand =
new DelegateCommand(o => this.worker.RunWorkerAsync(),
o => !this.worker.IsBusy);

this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}

// your UI binds to this command in order to kick off the work
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}

public int CurrentProgress
{
get { return this.currentProgress; }
private set
{
if (this.currentProgress != value)
{
this.currentProgress = value;
this.OnPropertyChanged(() => this.CurrentProgress);
}
}
}

private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
}

private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
}

Implementing a progress bar with MVVM / WPF

Your Execute() method can have an additional paramter of type IProgress<T>. The interface exposes Report(T value).

internal void Execute(IProgress<int> progress)
{
for(int x=0; x < 1000; x++){
progress.Report(x);
// perform function
}
}

On client side, where you want to inform the user of any progress, you can subscribe to ProgressChanged event. Or, you can pass a delegate to Progress class constructor:

// Create the progress object.
Progress<int> progress = new Progress<int>((result) =>
{
this.MyMethodToDisplayProgressToUser(result);
});

// Pass it to your method.
Execute(progress);

Note the IProgress<T> is a generic interface so instead of int you can have your reports e.g. in string format saying "Just started...", "Half of the job is done...", and so on.

Implementing Progress bar using WPF with MVVM pattern (Using BackgroundWorker)

The StartNowCommand never invokes the BackgroundWorker - it just synchronously executes the DoStartNow method on the UI thread. Based on this, I'd guess that when you click on the button linked to the StartNow command you see your UI freeze up..?

You should be binding your button to the InstigateWorkCommand which actually runs BackgroundWorker code asynchronously.

In this implementation I don't think you would need the StartNowCommand at all. I also don't see the DoWork event handler anywhere in your view model, so I'm assuming that it just calls DoStartNow

Display and hide progress bar using MVVM pattern

When you call api that time you have to take one live variable which shows your api is in loading mode or not and after success or failure you have to update that variable.

After observe that variable in your activity or fragment class and show or hide your progress.

public class LoginRepository {

private DATAModel dataModel = new DATAModel();
private MutableLiveData<DATAModel> mutableLiveData = new MutableLiveData<>();
private Application application;
private MutableLiveData<Boolean> progressbarObservable;

public LoginRepository(Application application) {
this.application = application;
}

public MutableLiveData<DATAModel> getMutableLiveData(String username, String password) {
// add below line
progressbarObservable.value = true
APIRequest apiRequest = RetrofitRequest.getRetrofit().create(APIRequest.class);

JsonLogin jsonLogin = new JsonLogin(Constants.DEVICE_TYPE, Functions.getDeviceId(application.getApplicationContext()), Constants.APP_VERSION, Constants.API_VERSION, Functions.getTimeStamp(), Functions.getDeviceModel(), Build.VERSION.RELEASE, username, password);

Call<APIResponseLogin> call = apiRequest.getUsersDetails(jsonLogin);
call.enqueue(new Callback<APIResponseLogin>() {
@Override
public void onResponse(Call<APIResponseLogin> call, Response<APIResponseLogin> response) {
// add below line
progressbarObservable.value = false
APIResponseLogin apiResponse = response.body();
if (apiResponse != null && apiResponse.getStatuscode() == 0) {
if (apiResponse.getDataModel() != null) {
dataModel = apiResponse.getDataModel();
mutableLiveData.setValue(dataModel);
}

} else if (apiResponse != null && apiResponse.getStatuscode() == 1) {

Log.v("AAAAAAAAA", apiResponse.getStatusmessage());
}
}

@Override
public void onFailure(Call<APIResponseLogin> call, Throwable t) {
// add below line
progressbarObservable.value = false
Log.v("ErrorResponse", t.getMessage() + " : " + call.request().toString());
}
});
return mutableLiveData;
}
}

Now, observe above variable in activity or fragment and based on that value hide or show your progress bar

    public class LoginActivity extends AppCompatActivity {
...

@Override
protected void onCreate(Bundle savedInstanceState) {
...

observeLogin();
}

@Override
public void onClick(View view)
{
switch (view.getId()) {
case R.id.button_login:
// Do something
loginCall();
}
}

private void observeLogin() {
loginViewModel.progressbarObservable().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(final Boolean progressObserve) {
if(progressObserve){
show your progress
}
else {
hide your progress
}
}
});
}

void loginCall() {
loginViewModel.getUserDetails(editTextUsername.getText().toString().trim(), editTextPassword.getText().toString().trim()).observe(this, new Observer<DATAModel>() {
@Override
public void onChanged(@Nullable DATAModel dataModel) {
if (dataModel != null) {
Userdetails userdetails = dataModel.getUserdetails();
List<ContactTypes> contactTypes = dataModel.getContactTypes();
if (userdetails != null) {

MySharedPreferences.setCustomPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USERDETAILS, userdetails);
MySharedPreferences.setStringPreference(LoginActivity.this, Constants.SHAREDPREFERENCE_USER_ID, userdetails.getUserId());
}

if (contactTypes != null) {
MySharedPreferences.setCustomArrayList(LoginActivity.this, Constants.SHAREDPREFERENCE_CONTACTTYPES, contactTypes);
}

Intent i = new Intent(LoginActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
});
}

}

Progress-bar MVVM?

When you're not explicitly indicating source object for your bindings (by means of Binding.Source or Binding.RelativeSource properties), the framework uses (possibly inherited) value of DataContext of the target object as the source. The problem is that you don't assign your view-model to the DataContext property of any control. Thus, the source for the bindings resolves to null and nothing is showing on your progress bar.

To resolve your issue you should assign your view model to the DataContext of your MainWindow:

public MainWindow()
{
InitializeComponent();
PsVm = new ProggressbarViewModel();
DataContext = PsVm;
}

If however you're planning on using different DataContext for your window, you can bind DataContext of ProgressBar:

<ProgressBar
DataContext="{Binding Path=PsVm,
RelativeSource={RelativeSource AncestorType=local:MainWindow}}"
(...) />

You could also modify particular bindings by prepending PsVm. to the value of Path and using RelativeSource, e.g.:

 Value="{Binding Path=PsVm.Progress,
RelativeSource={RelativeSource AncestorType=local:MainWindow},
Mode=OneWay}"

In that case however you'd have to modify each binding, so previous solutions are quicker and/or simpler.

As a side note, you might also want to change the way you're reporting progress - note that OnPropertyChanged in your DoWork method is not called from UI thread. The proper way to do it would be:

private void DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
worker.ReportProgress(i); //This will raise BackgroundWorker.ProgressChanged
}
}

Also, in order to support progress reporting, you should set WorkerReportsProgress to true on your worker, e.g.:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

How do I update ProgressBar from the Model in MVVM?

Thanks to @Adam Vincent, I ended up using the Messenger Pattern - specifically this implementation. I can now pass the ProgressBar value from the Model to the View, without breaking abstraction layers.

How do I add progressBar when getting api data through Retrofit? I am using MVVM pattern.The data is fetching but now I want to show the progress bar

One option is to add a new variable in ViewModel val loading = MutableLiveData<Boolean>() and then observe it in view. For your example

ViewModel

val loading = MutableLiveData<Boolean>()

fun getProgramData() {
loading.postValue(true)

val response=campDataRepository.getAllPrograms()

response.enqueue(object : Callback<List<Programs>?> {
override fun onResponse(
call: Call<List<Programs>?>,
response: Response<List<Programs>?>
) {
programList.postValue(response.body())
loading.postValue(false)
}

override fun onFailure(call: Call<List<Programs>?>, t: Throwable) {
errorMessage.postValue(t.message)
loading.postValue(false)
}
})
}

Fragment

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

viewModel.loading.observe(this, Observer {
when(it) {
true -> progressBar.visibility = View.Visible
false -> progressBar.visibility = View.Gone
})
}


Related Topics



Leave a reply



Submit