From b8d529d2d19481814ca78e50db3406f8bc6ecbb1 Mon Sep 17 00:00:00 2001 From: Christopher Beckmann Date: Thu, 14 May 2020 14:38:18 +0200 Subject: [PATCH] Fixed database crashes and operation on main thread. Added temporary fix to #76. It's missing a propper check if the country code is supported. Allowed calls on main thread for now to improve stability and reduce crashes. When the database call structure has been redone, this should be switched off again. Fixes #75, Fixes #65 (fixed with the key generation change) --- .../database/ApplicationDatabase.java | 1 + .../database/ConsumedEntriesDao.java | 1 - .../database/ProductDao.java | 7 +- .../network/ApiManager.java | 7 +- .../ui/OverviewActivity.java | 79 ++++++------ .../ui/SearchFoodFragment.java | 4 +- .../ui/adapter/DatabaseFacade.java | 121 +++++++++--------- .../ui/viewmodels/OverviewViewModel.java | 58 +++++++++ app/src/main/res/layout/activity_overview.xml | 6 +- app/src/main/res/layout/content_overview.xml | 1 + 10 files changed, 176 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/viewmodels/OverviewViewModel.java diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ApplicationDatabase.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ApplicationDatabase.java index 0a78ff5..7d62ea8 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ApplicationDatabase.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ApplicationDatabase.java @@ -59,6 +59,7 @@ public static ApplicationDatabase getInstance(final Context context) throws Exce sInstance = Room.databaseBuilder(context.getApplicationContext(),ApplicationDatabase.class, DATABASE_NAME) .openHelperFactory(factory) + .allowMainThreadQueries() .addCallback(new Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ConsumedEntriesDao.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ConsumedEntriesDao.java index ff6e2d4..80f1498 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ConsumedEntriesDao.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ConsumedEntriesDao.java @@ -62,7 +62,6 @@ public interface ConsumedEntriesDao { @Query("SELECT * FROM consumedEntries WHERE date=:date") List findConsumedEntriesForDate(final Date date); - @Query("DELETE FROM consumedEntries") void deleteAll(); } diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ProductDao.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ProductDao.java index d7e40bb..3e6b0b1 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ProductDao.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/database/ProductDao.java @@ -16,6 +16,7 @@ */ package org.secuso.privacyfriendlyfoodtracker.database; +import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Delete; import android.arch.persistence.room.Insert; @@ -24,6 +25,7 @@ import java.sql.Date; import java.util.List; +import java.util.concurrent.Future; /** * Includes methods that offer abstract access to the app database to manage products. @@ -45,17 +47,14 @@ public interface ProductDao { void deleteAll(); @Query("SELECT * FROM product") - List getAllProducts(); + LiveData> getAllProducts(); @Query("SELECT * FROM product WHERE id=:id") Product findProductById(final int id); - @Query("SELECT * FROM product WHERE name=:name AND energy=:energy AND barcode=:barcode") List findExistingProducts(String name, float energy, String barcode); @Query("SELECT * FROM product WHERE name LIKE :name") List findProductsByName(String name); - - } diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/network/ApiManager.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/network/ApiManager.java index b0b4bd9..77634ef 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/network/ApiManager.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/network/ApiManager.java @@ -74,10 +74,9 @@ public ProductApiService getProductApiService(String languageCode) { } private ProductApiService createProductApiService() { - String languageCode = Locale.getDefault().getLanguage(); - if (languageCode == "de") { - languageCode = "de"; - } else { + String languageCode = Locale.getDefault().getCountry(); + // TODO: check if it supported -> https://world.openfoodfacts.org/data/taxonomies/countries.json + if (languageCode.equals("")) { languageCode = "world"; } productApiService = createProductApiService(languageCode); diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/OverviewActivity.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/OverviewActivity.java index e29ccaf..60159bb 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/OverviewActivity.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/OverviewActivity.java @@ -18,11 +18,17 @@ import org.secuso.privacyfriendlyfoodtracker.ui.adapter.DatabaseEntry; import org.secuso.privacyfriendlyfoodtracker.ui.adapter.DatabaseFacade; +import org.secuso.privacyfriendlyfoodtracker.ui.viewmodels.OverviewViewModel; +import org.secuso.privacyfriendlyfoodtracker.ui.viewmodels.SharedStatisticViewModel; import org.secuso.privacyfriendlyfoodtracker.ui.views.CheckableCardView; import org.secuso.privacyfriendlyfoodtracker.R; import android.app.AlertDialog; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; import android.support.v7.widget.CardView; import android.text.InputFilter; @@ -68,6 +74,8 @@ public class OverviewActivity extends AppCompatActivity { // I.e. if more than 0 cards are selected, the user should be able to delete them private int selectedCards; + private OverviewViewModel viewModel; + /** * sets up the activity * @param savedInstanceState the saved instance state @@ -79,6 +87,17 @@ protected void onCreate(Bundle savedInstanceState) { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbarOverview); setSupportActionBar(toolbar); + viewModel = ViewModelProviders.of(this).get(OverviewViewModel.class); + refreshData(); + + viewModel.getList().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List databaseEntries) { + refreshFoodList(databaseEntries); + refreshTotalCalorieCounter(databaseEntries); + } + }); + // Set up global variables // Set the date. If no date is passed on, the system date is chosen by default. Intent intent = getIntent(); @@ -96,8 +115,7 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onResume() { super.onResume(); - refreshFoodList(); - refreshTotalCalorieCounter(); + refreshData(); } /** @@ -121,10 +139,14 @@ public boolean onOptionsItemSelected(MenuItem item) { } selectedCards = 0; toggleDeletionMenuVisibility(); - refreshTotalCalorieCounter(); + refreshData(); return true; } + private void refreshData() { + viewModel.init(getDateForActivity()); + } + /** * set the menu to say the correct things * @param menu the menu @@ -224,8 +246,7 @@ private void deleteSelectedCards() { if (c.isChecked()) { TextView idView = (TextView) c.getChildAt(1); String id = idView.getText().toString(); - DatabaseFacade facade = getDbFacade(); - facade.deleteEntryById(Integer.parseInt(id)); + viewModel.deleteEntryById(Integer.parseInt(id)); cardsToRemove.add(v); } } @@ -245,37 +266,21 @@ private ViewGroup getEntryList() { return foodlist; } - /** - * creates a database facade that can be used to call database functions - * - * @return a DatabaseFacade object - */ - private DatabaseFacade getDbFacade() { - DatabaseFacade facade; - try { - facade = new DatabaseFacade(this); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - return facade; - } - /** * Refreshes the calorie counter at the top of the activity. * Recounts calories for all Entries of the day. */ - private void refreshTotalCalorieCounter() { + private void refreshTotalCalorieCounter(@Nullable List entries) { BigDecimal totalCalories = new BigDecimal("0"); TextView heading = this.findViewById(R.id.overviewHeading); String cal = getString(R.string.total_calories); Date d = getDateForActivity(); String formattedDate = getFormattedDate(d); - DatabaseFacade facade = getDbFacade(); - DatabaseEntry[] entries = facade.getEntriesForDay(d); - for (DatabaseEntry e : entries) { - totalCalories = totalCalories.add( BigDecimal.valueOf(e.energy * e.amount/ 100) ); - // totalCalories += (e.energy * e.amount) / 100; + if(entries != null) { + for (DatabaseEntry e : entries) { + totalCalories = totalCalories.add(BigDecimal.valueOf(e.energy * e.amount / 100)); + // totalCalories += (e.energy * e.amount) / 100; + } } heading.setText(String.format(Locale.ENGLISH, " %s: %.2f %s", formattedDate, totalCalories, cal)); } @@ -340,7 +345,7 @@ public void onClick(View v) { deleteSelectedCards(); selectedCards = 0; toggleDeletionMenuVisibility(); - refreshTotalCalorieCounter(); + refreshData(); } }); @@ -489,9 +494,8 @@ public void onClick(View v) { public void onClick(DialogInterface dialog, int whichButton) { String amountField = input.getText().toString(); if(amountField.length() != 0 ){ - boolean result = editDatabaseEntry(amountField, entry.id); - refreshFoodList(); - refreshTotalCalorieCounter(); + editDatabaseEntry(amountField, entry.id); + refreshData(); } } }); @@ -520,22 +524,17 @@ public void onClick(DialogInterface dialog, int whichButton) { * @param idString the id of the entry * @return true if the entry was successful */ - private boolean editDatabaseEntry(String amountString, String idString) { - DatabaseFacade facade = getDbFacade(); + private void editDatabaseEntry(String amountString, String idString) { int amount = Integer.parseInt(amountString); int id = Integer.parseInt(idString); - return facade.editEntryById(id, amount); + viewModel.editEntryById(id, amount); } /** * refresh the list of entries */ - private void refreshFoodList() { - Date d = getDateForActivity(); - DatabaseFacade facade = getDbFacade(); - // Fix Issue #53 - if(facade != null) { - DatabaseEntry[] entries = facade.getEntriesForDay(d); + private void refreshFoodList(List entries) { + if(entries != null) { ViewGroup foodList = getEntryList(); foodList.removeAllViews(); for (DatabaseEntry e : entries) { diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/SearchFoodFragment.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/SearchFoodFragment.java index a1a2d9f..9fbc43f 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/SearchFoodFragment.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/SearchFoodFragment.java @@ -229,7 +229,9 @@ public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void onTextChanged(CharSequence s, int start, int before, int count) { // search in the local db first System.out.println(s.toString()); - SearchResultAdapter newAdapter = new SearchResultAdapter(databaseFacade.getProductByName(s.toString())); + SearchResultAdapter newAdapter = new SearchResultAdapter( + databaseFacade.getProductByName(s.toString()) + ); foodList.setAdapter(newAdapter); } diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/adapter/DatabaseFacade.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/adapter/DatabaseFacade.java index d7b955c..36510c4 100644 --- a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/adapter/DatabaseFacade.java +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/adapter/DatabaseFacade.java @@ -16,6 +16,7 @@ */ package org.secuso.privacyfriendlyfoodtracker.ui.adapter; +import android.arch.lifecycle.LiveData; import android.content.Context; import android.util.Log; @@ -28,6 +29,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; /** * Database access functions. @@ -51,63 +54,64 @@ public DatabaseFacade(Context context) throws Exception { * @param date the consumption date in UNIX format * @param name the name * @param productId the consumed product id - * @return true if no error occurs */ - public boolean insertEntry(int amount, java.util.Date date, String name, float energy, int productId){ - int existingProductId = 0; - //If the productId is 0 we need to create a new product in the database - if (0 == productId) { - insertProduct(name, energy, ""); - // retrieve ProductId of newly created Product from database - List existingProducts = productDao.findExistingProducts(name, energy, ""); - // There is only one existing product so we take the first one from the List - Product p = existingProducts.get(0); - existingProductId = p.id; - } else { - existingProductId = productId; - } - try { - consumedEntriesDao.insert(new ConsumedEntries(0, amount, new java.sql.Date(date.getTime()), name, existingProductId)); - return true; - } catch (Exception e){ - return false; - } + public void insertEntry(final int amount, final java.util.Date date, final String name, final float energy, final int productId){ + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + int existingProductId = 0; + //If the productId is 0 we need to create a new product in the database + if (0 == productId) { + insertProductPrivate(name, energy, ""); + // retrieve ProductId of newly created Product from database + List existingProducts = productDao.findExistingProducts(name, energy, ""); + // There is only one existing product so we take the first one from the List + Product p = existingProducts.get(0); + existingProductId = p.id; + } else { + existingProductId = productId; + } + consumedEntriesDao.insert(new ConsumedEntries(0, amount, new java.sql.Date(date.getTime()), name, existingProductId)); + } + }); } /** * Deletes a database entry by id. * @param id the id - * @return successfully or not */ - public boolean deleteEntryById(int id ){ - try { - List res = consumedEntriesDao.findConsumedEntriesById(id); - if(res.size() != 1){return false;} - consumedEntriesDao.delete(res.get(0)); - return true; - } catch (Exception e){ - return false; - } + public void deleteEntryById(final int id){ + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + List res = consumedEntriesDao.findConsumedEntriesById(id); + if (res.size() != 1) { + return; + } + consumedEntriesDao.delete(res.get(0)); + } + }); } /** * Edit a database entry. * @param id the id * @param amount the new amount - * @return successfully or not */ - public boolean editEntryById(int id, int amount){ - try { - List res = consumedEntriesDao.findConsumedEntriesById(id); - if(res.size() != 1){return false;} - ConsumedEntries consumedEntry = res.get(0); - consumedEntry.amount = amount; - consumedEntriesDao.update(res.get(0)); - return true; - } catch (Exception e){ - return false; - } + public void editEntryById(final int id, final int amount){ + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + List res = consumedEntriesDao.findConsumedEntriesById(id); + if (res.size() != 1) { + return; + } + ConsumedEntries consumedEntry = res.get(0); + consumedEntry.amount = amount; + consumedEntriesDao.update(res.get(0)); + } + }); } /** @@ -115,19 +119,22 @@ public boolean editEntryById(int id, int amount){ * @param name the name * @param energy the energy * @param barcode the barcode - * @return successfully or not */ - public boolean insertProduct( String name, float energy, String barcode){ - try{ - List res = productDao.findExistingProducts(name, energy, barcode); - if(res.size() != 0){ - return false; + public void insertProduct(final String name, final float energy, final String barcode){ + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + insertProductPrivate(name, energy, barcode); } - productDao.insert(new Product(0,name, energy, barcode)); - return true; - }catch (Exception e){ - return false; + }); + } + + private void insertProductPrivate(String name, float energy, String barcode) { + List res = productDao.findExistingProducts(name, energy, barcode); + if (res.size() != 0) { + return; } + productDao.insert(new Product(0, name, energy, barcode)); } /** @@ -141,7 +148,6 @@ public List findMostCommonProducts() { for (int i = 0; i < res.size(); i++) { products.add(productDao.findProductById(res.get(i))); } - } catch (Exception e) { Log.e("DatabaseFacade", "Error o"); } @@ -153,12 +159,11 @@ public List findMostCommonProducts() { * @param date the date * @return DatabaseEntry */ - public DatabaseEntry[] getEntriesForDay(java.util.Date date) { + public List getEntriesForDay(java.util.Date date) { List databaseEntries = new ArrayList<>(); try { List res = consumedEntriesDao.findConsumedEntriesForDate(new java.sql.Date(date.getTime())); - for (int i = 0; i < res.size(); i++) { - ConsumedEntries consumedEntry = res.get(i); + for(ConsumedEntries consumedEntry : res) { Product product = productDao.findProductById(consumedEntry.productId); databaseEntries.add(new DatabaseEntry(String.valueOf(consumedEntry.id),consumedEntry.name, consumedEntry.amount, product.energy)); } @@ -166,7 +171,7 @@ public DatabaseEntry[] getEntriesForDay(java.util.Date date) { } catch (Exception e) { Log.e("DatabaseFacade", "Error o"); } - return databaseEntries.toArray(new DatabaseEntry[databaseEntries.size()]); + return databaseEntries; } /** @@ -186,7 +191,7 @@ public List getPeriodCalories(java.uti * @return the calories sum (list position 0) */ public List getCaloriesPerDayinPeriod(java.util.Date startDate, java.util.Date endDate){ - return consumedEntrieAndProductDao.getCaloriesPerDayinPeriod(new java.sql.Date(startDate.getTime()), new java.sql.Date(endDate.getTime())); + return consumedEntrieAndProductDao.getCaloriesPerDayinPeriod(new java.sql.Date(startDate.getTime()), new java.sql.Date(endDate.getTime())); } /** diff --git a/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/viewmodels/OverviewViewModel.java b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/viewmodels/OverviewViewModel.java new file mode 100644 index 0000000..8c7f70e --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlyfoodtracker/ui/viewmodels/OverviewViewModel.java @@ -0,0 +1,58 @@ +package org.secuso.privacyfriendlyfoodtracker.ui.viewmodels; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProvider; +import android.support.annotation.NonNull; +import android.util.Log; + +import org.secuso.privacyfriendlyfoodtracker.database.ConsumedEntries; +import org.secuso.privacyfriendlyfoodtracker.database.Product; +import org.secuso.privacyfriendlyfoodtracker.ui.adapter.DatabaseEntry; +import org.secuso.privacyfriendlyfoodtracker.ui.adapter.DatabaseFacade; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Executors; + +public class OverviewViewModel extends AndroidViewModel { + private MutableLiveData> list = new MutableLiveData<>(); + + DatabaseFacade dbHelper = null; + + public OverviewViewModel(@NonNull Application application) { + super(application); + + try { + dbHelper = new DatabaseFacade(application.getApplicationContext()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void init(final Date day) { + Executors.newSingleThreadExecutor().execute(new Runnable() { + @Override + public void run() { + list.postValue(dbHelper.getEntriesForDay(day)); + } + }); + } + + public LiveData> getList() { + return list; + } + + + public void deleteEntryById(int id) { + dbHelper.deleteEntryById(id); + } + + public void editEntryById(int id, int amount) { + dbHelper.editEntryById(id, amount); + } +} diff --git a/app/src/main/res/layout/activity_overview.xml b/app/src/main/res/layout/activity_overview.xml index 321a9ef..c14ea14 100644 --- a/app/src/main/res/layout/activity_overview.xml +++ b/app/src/main/res/layout/activity_overview.xml @@ -31,9 +31,12 @@ android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" app:srcCompat="@drawable/add_entry" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_overview.xml b/app/src/main/res/layout/content_overview.xml index 708cea9..afba288 100644 --- a/app/src/main/res/layout/content_overview.xml +++ b/app/src/main/res/layout/content_overview.xml @@ -37,5 +37,6 @@ android:layout_height="wrap_content" android:minHeight="14dp" android:orientation="vertical" /> + \ No newline at end of file