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
Linq to SQL: Multiple Joins on Multiple Columns. Is This Possible
Creating a PDF from a Rdlc Report in the Background
What Is the Purpose of Anonymous { } Blocks in C Style Languages
How to Add Js and CSS Files in ASP.NET Core
Unitywebrequest Embedding User + Password Data for Http Basic Authentication Not Working on Android
Creating a "Hello World" Websocket Example
How to Use the Cancellationtoken Property
Auto-Implemented Getters and Setters VS. Public Fields
What Is the Effect of Asenumerable() on a Linq Entity
Understanding Wcf Windows Authentication
Raise Event in High Resolution Interval/Timer
Difference Between Wiring Events with and Without "New"
Best Way to Combine Two or More Byte Arrays in C#
Best Way to Deploy Visual Studio Application That Can Run Without Installing