1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Use a consumable LiveData event to report the schedule download result to UI in place of LocalBroadcastManager

Use a simple AtomicBoolean instead of a lock to prevent concurrent downloads
This commit is contained in:
Christophe Beyls 2019-01-24 20:20:50 +01:00
parent 2a7b1439cc
commit 7e15f7de55
4 changed files with 131 additions and 87 deletions

View file

@ -4,17 +4,10 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Dialog;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.*;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.format.DateUtils;
@ -23,10 +16,6 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -53,11 +42,11 @@ import be.digitalia.fosdem.R;
import be.digitalia.fosdem.api.FosdemApi;
import be.digitalia.fosdem.api.FosdemUrls;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.fragments.BookmarksListFragment;
import be.digitalia.fosdem.fragments.LiveFragment;
import be.digitalia.fosdem.fragments.MapFragment;
import be.digitalia.fosdem.fragments.PersonsListFragment;
import be.digitalia.fosdem.fragments.TracksFragment;
import be.digitalia.fosdem.fragments.*;
import be.digitalia.fosdem.livedata.SingleEvent;
import be.digitalia.fosdem.model.DownloadScheduleResult;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
/**
* Main entry point of the application. Allows to switch between section fragments and update the database.
@ -136,24 +125,26 @@ public class MainActivity extends AppCompatActivity {
private MenuItem searchMenuItem;
private final BroadcastReceiver scheduleDownloadResultReceiver = new BroadcastReceiver() {
private final Observer<SingleEvent<DownloadScheduleResult>> scheduleDownloadResultObserver = new Observer<SingleEvent<DownloadScheduleResult>>() {
@Override
public void onReceive(Context context, Intent intent) {
int result = intent.getIntExtra(FosdemApi.EXTRA_RESULT, FosdemApi.RESULT_ERROR);
public void onChanged(SingleEvent<DownloadScheduleResult> singleEvent) {
final DownloadScheduleResult result = singleEvent.consume();
if (result == null) {
return;
}
String message;
switch (result) {
case FosdemApi.RESULT_ERROR:
message = getString(R.string.schedule_loading_error);
break;
case FosdemApi.RESULT_UP_TO_DATE:
message = getString(R.string.events_download_up_to_date);
break;
case 0:
if (result.isError()) {
message = getString(R.string.schedule_loading_error);
} else if (result.isUpToDate()) {
message = getString(R.string.events_download_up_to_date);
} else {
int eventsCount = result.getEventsCount();
if (eventsCount == 0) {
message = getString(R.string.events_download_empty);
break;
default:
message = getResources().getQuantityString(R.plurals.events_download_completed, result, result);
} else {
message = getResources().getQuantityString(R.plurals.events_download_completed, eventsCount, eventsCount);
}
}
Snackbar.make(findViewById(R.id.content), message, Snackbar.LENGTH_LONG).show();
}
@ -172,14 +163,14 @@ public class MainActivity extends AppCompatActivity {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
return new AlertDialog.Builder(getContext())
.setTitle(R.string.download_reminder_title)
.setMessage(R.string.download_reminder_message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
((MainActivity) getActivity()).startDownloadSchedule();
FosdemApi.downloadSchedule(getContext());
}
}).setNegativeButton(android.R.string.cancel, null)
@ -234,6 +225,9 @@ public class MainActivity extends AppCompatActivity {
}
});
// Monitor the schedule download result
FosdemApi.getDownloadScheduleResult().observe(this, scheduleDownloadResultObserver);
// Setup drawer layout
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
drawerLayout = findViewById(R.id.drawer_layout);
@ -296,7 +290,7 @@ public class MainActivity extends AppCompatActivity {
updateLastUpdateTime();
if (savedInstanceState == null) {
// Select initial section
// Select initial section
currentSection = Section.TRACKS;
String action = getIntent().getAction();
if (action != null) {
@ -375,10 +369,6 @@ public class MainActivity extends AppCompatActivity {
protected void onStart() {
super.onStart();
// Monitor the schedule download result
LocalBroadcastManager.getInstance(this).registerReceiver(scheduleDownloadResultReceiver,
new IntentFilter(FosdemApi.ACTION_DOWNLOAD_SCHEDULE_RESULT));
// Download reminder
long now = System.currentTimeMillis();
long time = DatabaseManager.getInstance().getLastUpdateTime();
@ -404,8 +394,6 @@ public class MainActivity extends AppCompatActivity {
searchMenuItem.collapseActionView();
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(scheduleDownloadResultReceiver);
super.onStop();
}
@ -444,35 +432,16 @@ public class MainActivity extends AppCompatActivity {
item.setIcon(icon);
((Animatable) icon).start();
}
startDownloadSchedule();
FosdemApi.downloadSchedule(this);
return true;
}
return false;
}
public void startDownloadSchedule() {
new DownloadScheduleAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private static class DownloadScheduleAsyncTask extends AsyncTask<Void, Void, Void> {
private final Context appContext;
public DownloadScheduleAsyncTask(Context context) {
appContext = context.getApplicationContext();
}
@Override
protected Void doInBackground(Void... args) {
FosdemApi.downloadSchedule(appContext);
return null;
}
}
// MAIN MENU
void handleNavigationMenuItem(@NonNull MenuItem menuItem) {
final int menuItemId = menuItem.getItemId();
final int menuItemId = menuItem.getItemId();
final Section section = Section.fromMenuItemId(menuItemId);
if (section != null) {
selectMenuSection(section, menuItem);

View file

@ -1,24 +1,22 @@
package be.digitalia.fosdem.api;
import android.content.Context;
import android.content.Intent;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import android.os.AsyncTask;
import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import be.digitalia.fosdem.BuildConfig;
import be.digitalia.fosdem.db.DatabaseManager;
import be.digitalia.fosdem.livedata.SingleEvent;
import be.digitalia.fosdem.model.DownloadScheduleResult;
import be.digitalia.fosdem.model.Event;
import be.digitalia.fosdem.model.RoomStatus;
import be.digitalia.fosdem.parsers.EventsParser;
import be.digitalia.fosdem.utils.HttpUtils;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Main API entry point.
*
@ -26,31 +24,36 @@ import be.digitalia.fosdem.utils.HttpUtils;
*/
public class FosdemApi {
// Local broadcasts parameters
public static final String ACTION_DOWNLOAD_SCHEDULE_RESULT = BuildConfig.APPLICATION_ID + ".action.DOWNLOAD_SCHEDULE_RESULT";
public static final String EXTRA_RESULT = "RESULT";
public static final int RESULT_ERROR = -1;
public static final int RESULT_UP_TO_DATE = -2;
private static final Lock scheduleLock = new ReentrantLock();
private static final AtomicBoolean isLoading = new AtomicBoolean();
private static final MutableLiveData<Integer> progress = new MutableLiveData<>();
private static final MutableLiveData<SingleEvent<DownloadScheduleResult>> result = new MutableLiveData<>();
private static LiveData<Map<String, RoomStatus>> roomStatuses;
/**
* Download & store the schedule to the database.
* Only one thread at a time will perform the actual action, the other ones will return immediately.
* The result will be sent back in the form of a local broadcast with an ACTION_DOWNLOAD_SCHEDULE_RESULT action.
* The result will be sent back in the consumable Result LiveData.
*/
@WorkerThread
@MainThread
public static void downloadSchedule(Context context) {
if (!scheduleLock.tryLock()) {
if (!isLoading.compareAndSet(false, true)) {
// If a download is already in progress, return immediately
return;
}
final Context appContext = context.getApplicationContext();
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
downloadScheduleInternal(appContext);
isLoading.set(false);
}
});
}
@WorkerThread
private static void downloadScheduleInternal(Context context) {
progress.postValue(-1);
int result = RESULT_ERROR;
DownloadScheduleResult res = DownloadScheduleResult.error();
try {
DatabaseManager dbManager = DatabaseManager.getInstance();
HttpUtils.HttpResult httpResult = HttpUtils.get(
@ -64,13 +67,14 @@ public class FosdemApi {
});
if (httpResult.inputStream == null) {
// Nothing to parse, the result is up-to-date.
result = RESULT_UP_TO_DATE;
res = DownloadScheduleResult.upToDate();
return;
}
try {
Iterable<Event> events = new EventsParser().parse(httpResult.inputStream);
result = dbManager.storeSchedule(events, httpResult.lastModified);
int count = dbManager.storeSchedule(events, httpResult.lastModified);
res = DownloadScheduleResult.success(count);
} finally {
try {
httpResult.inputStream.close();
@ -80,12 +84,10 @@ public class FosdemApi {
} catch (Exception e) {
e.printStackTrace();
res = DownloadScheduleResult.error();
} finally {
progress.postValue(100);
Intent resultIntent = new Intent(ACTION_DOWNLOAD_SCHEDULE_RESULT)
.putExtra(EXTRA_RESULT, result);
LocalBroadcastManager.getInstance(context).sendBroadcast(resultIntent);
scheduleLock.unlock();
result.postValue(new SingleEvent<>(res));
}
}
@ -99,6 +101,10 @@ public class FosdemApi {
return progress;
}
public static LiveData<SingleEvent<DownloadScheduleResult>> getDownloadScheduleResult() {
return result;
}
@MainThread
public static LiveData<Map<String, RoomStatus>> getRoomStatuses() {
if (roomStatuses == null) {

View file

@ -0,0 +1,28 @@
package be.digitalia.fosdem.livedata;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Encapsulates data that can only be consumed once.
*/
public class SingleEvent<T> {
private T content;
public SingleEvent(@NonNull T content) {
this.content = content;
}
/**
* @return The content, or null if it has already been consumed.
*/
@Nullable
public T consume() {
final T previousContent = content;
if (previousContent != null) {
content = null;
}
return previousContent;
}
}

View file

@ -0,0 +1,41 @@
package be.digitalia.fosdem.model;
public class DownloadScheduleResult {
private static final DownloadScheduleResult RESULT_ERROR = new DownloadScheduleResult(0);
private static final DownloadScheduleResult RESULT_UP_TO_DATE = new DownloadScheduleResult(0);
private final int eventsCount;
private DownloadScheduleResult(int eventsCount) {
this.eventsCount = eventsCount;
}
public static DownloadScheduleResult success(int eventsCount) {
return new DownloadScheduleResult(eventsCount);
}
public static DownloadScheduleResult error() {
return RESULT_ERROR;
}
public static DownloadScheduleResult upToDate() {
return RESULT_UP_TO_DATE;
}
public boolean isSuccess() {
return this != RESULT_ERROR && this != RESULT_UP_TO_DATE;
}
public boolean isError() {
return this == RESULT_ERROR;
}
public boolean isUpToDate() {
return this == RESULT_UP_TO_DATE;
}
public int getEventsCount() {
return eventsCount;
}
}