I am writing an application to control personal budgets. How it is designed now is basically using two different ExpandableListViews, one for incomes, the other for expenses. Inflating Dialogs for setting up the categories of incomes/expenses, as well as the transactions. The application stores the data in an SQLite database.
So far, so good. My questions come when I try to implement some kind of design to organize this data by different months. I thought about ViewPager, because it is the option that I think it is most close to this implementation. I am quite new in Android development so not sure if there is a better option to implement this functionality.
The Activity and Fragment for this view are the followings:
SummaryActivity.java
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import com.robin.xbudget.database.TransactionBaseHelper;
import com.robin.xbudget.database.TransactionDbSchema;
import com.robin.xbudget.database.TransactionDbSchema.TransactionTable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SummaryActivity extends SingleFragmentActivity implements
NewCategoryIncome.NewCategoryActivityListener
, NewTransactionIncome.NewIncomeActivityListener
, NewCategoryExpense.NewCategoryActivityListener
, NewTransactionExpense.NewExpenseActivityListener
, SummaryFragment.SummaryFragmentActivityListener
, Statistics.StatisticsListener {
/**
* Constants
*/
private static final String TAG = "XSummaryActivity";
public static final String mSalaryKey = "salaryKey";
public static final String mCurrencyKey = "currencyKey";
/**
* Members
*/
private SQLiteDatabase mDatabase;
private Context mContext;
/**
* Incomes
*/
private List<String> listIncomesDataGroup;
private Map<String, List<Transaction>> listIncomesDataChild;
/**
* Expenses
*/
private List<String> listExpensesDataGroup;
private Map<String, List<Transaction>> listExpensesDataChild;
/**
* Expense Adapter
*/
private ExpandableListViewAdapter expandableExpensesListViewAdapter;
private ExpandableListViewAdapter expandableIncomesListViewAdapter;
/**
* Getters / Setters
*/
@Override
public List<String> getIncomesDataGroup() {
return listIncomesDataGroup;
}
@Override
public List<String> getExpensesDataGroup() {
return listExpensesDataGroup;
}
@Override
public Map<String, List<Transaction>> getExpensesDataChild() {
return listExpensesDataChild;
}
@Override
public ExpandableListViewAdapter getIncomesAdapter() {
return expandableIncomesListViewAdapter;
}
@Override
public ExpandableListViewAdapter getExpensesAdapter() {
return expandableExpensesListViewAdapter;
}
@Override
public double getTotalIncomes() {
double total = 0;
for (Map.Entry<String, List<Transaction>> set : listIncomesDataChild.entrySet()) {
for (Transaction t : set.getValue()) {
total += t.getQuantity();
}
}
Log.d(TAG, "Total of Income Transactions is: " + total);
return total;
}
@Override
public double getTotalExpenses() {
double total = 0;
for (Map.Entry<String, List<Transaction>> set : listExpensesDataChild.entrySet()) {
for (Transaction t : set.getValue()) {
total += t.getQuantity();
}
}
Log.d(TAG, "Total of Expense Transactions is: " + total);
return total;
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
protected Fragment createFragment() {
Log.d(TAG, "onCreateFragment() was called");
initObject();
return SummaryFragment.newInstance();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
Log.d(TAG, "onCreate() was called");
super.onCreate(savedInstanceState, persistentState);
}
@RequiresApi(api = Build.VERSION_CODES.N)
public void initObject() {
Log.d(TAG, "initObject() was called");
//Starting the databases
mContext = getApplicationContext();
mDatabase = new TransactionBaseHelper(mContext).getWritableDatabase();
// initializing the list of groups
listIncomesDataGroup = new ArrayList<>();
listExpensesDataGroup = new ArrayList<>();
// initializing the list of child
listIncomesDataChild = new HashMap<>();
listExpensesDataChild = new HashMap<>();
// initializing the adapter object
expandableIncomesListViewAdapter = new ExpandableListViewAdapter(getApplicationContext(), listIncomesDataGroup, listIncomesDataChild);
expandableExpensesListViewAdapter = new ExpandableListViewAdapter(getApplicationContext(), listExpensesDataGroup, listExpensesDataChild);
updateCategories();
updateChilds();
}
/***
* Listeners - (Interface Implementations)
*/
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void insertIncome(Transaction transaction) {
//String value = transaction + " " + mCurrency;
listIncomesDataChild.get(transaction.getCategory()).add(transaction);
//updateChilds();
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void insertExpense(Transaction transaction) {
ContentValues values = getContentValues(transaction);
Log.d(TAG, "TRANSACTION TO INSERT IS: " + transaction);
mDatabase.insert(TransactionTable.mTransactionExpense, null, values);
updateChilds();
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void updateExpense(Transaction transaction) {
ContentValues values = getContentValues(transaction);
String mIdString = transaction.getId().toString();
mDatabase.update(TransactionTable.mTransactionExpense, values, TransactionTable.ChildCols.UUID + " = ?", new String[]{mIdString});
updateChilds();
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void deleteExpense(Transaction transaction) {
String mIdString = transaction.getId().toString();
Log.d(TAG, "mIdString is: " + mIdString);
Log.d(TAG, "rows deleted are: " + mDatabase.delete(TransactionDbSchema.TransactionTable.mTransactionExpense, TransactionTable.ChildCols.UUID + " = ?", new String[]{mIdString}));
updateChilds();
}
@Override
public void insertCategoryExpense(String category) {
if (category != null || !category.trim().isEmpty()) {
mDatabase.execSQL("INSERT INTO " + TransactionTable.mCategoryExpense + " ('category') " + "VALUES (" + "'" + category + "'" + ")");
updateCategories();
}
}
private ContentValues getContentValues(Transaction transaction) {
ContentValues values = new ContentValues();
values.put(TransactionTable.ChildCols.CATEGORY, transaction.getCategory());
values.put(TransactionTable.ChildCols.UUID, transaction.getId().toString());
values.put(TransactionTable.ChildCols.NAME, transaction.getName());
values.put(TransactionTable.ChildCols.QUANTITY, transaction.getQuantity());
values.put(TransactionTable.ChildCols.DATE, transaction.getDate().getTime());
return values;
}
public List<String> getCategoryByTable(String table) {
List<String> categories = new ArrayList<>();
String query = "SELECT * from " + TransactionTable.mCategoryExpense;
Cursor c = mDatabase.rawQuery(query, null);
if (c.moveToFirst()) {
while (!c.isAfterLast()) {
categories.add(c.getString(0));
c.moveToNext();
}
}
return categories;
}
private TransactionCursorWrapper queryTransactions(String table, String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(
table
,
null, // columns - null selects all columns
whereClause,
whereArgs,
null, // groupBy
null, // having
null // orderBy
);
return new TransactionCursorWrapper(cursor);
}
@Override
public List<Transaction> getTransactionsByTable(String table) {
List<Transaction> transactions = new ArrayList<>();
TransactionCursorWrapper cursor = queryTransactions(table, null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
if (!transactions.contains(cursor.getTransaction()))
transactions.add(cursor.getTransaction());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return transactions;
}
@Override
public void updateCategories() {
List list = getCategoryByTable(TransactionTable.mCategoryExpense);
Log.d(TAG, "BEFORE: GROUP SIZE" + list.size());
listExpensesDataGroup.clear();
listExpensesDataGroup.addAll(list);
for (String category : listExpensesDataGroup)
if (!listExpensesDataChild.containsKey(category))
listExpensesDataChild.put(category, new ArrayList<Transaction>());
expandableExpensesListViewAdapter.notifyDataSetChanged();
}
@RequiresApi(api = Build.VERSION_CODES.N)
public void updateChilds() {
//Retrieve data from the DB
List<Transaction> transactionList = getTransactionsByTable(TransactionTable.mTransactionExpense);
//Clear lists inside the Map
for (Map.Entry<String, List<Transaction>> set : getExpensesDataChild().entrySet())
set.getValue().clear();
//Add the values to each List from the DB
for (Transaction transaction : transactionList) {
Log.d(TAG, "transaction.getCategory() value is: " + transaction.getCategory());
Log.d(TAG, "listener.getExpensesDataChild().get(transaction.getCategory())" + getExpensesDataChild().get(transaction.getCategory()));
listExpensesDataChild.get(transaction.getCategory()).add(transaction);
}
expandableExpensesListViewAdapter.notifyDataSetChanged();
/*
//Remove the category from the view if it has no data
//Working through a local List to avoid ConcurrentModificationException
List <String> localListExpensesDataGroup = new ArrayList<>();
if(!listExpensesDataGroup.isEmpty() || listExpensesDataGroup!=null)
{ for(String group:listExpensesDataGroup){
if(localListExpensesDataChild.containsKey(group)) {
localListExpensesDataGroup.add(group);
}
}
*/
}
}
SummaryFragment.java
package com.robin.xbudget;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ExpandableListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import java.util.List;
import java.util.Map;
public class SummaryFragment extends Fragment {
SummaryFragmentActivityListener listener;
Button newIncomeGroupBtn;
Button newExpenseGroupBtn;
Button newIncomeTransactionBtn;
Button newExpenseTransactionBtn;
/**
* Expandable views
*/
private ExpandableListView expandableIncomesListView;
private ExpandableListView expandableExpensesListView;
/**
* Static members / Constants
*/
private static final String TAG = "XSecondActivity";
public static SummaryFragment newInstance() {
return new SummaryFragment();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
listener = (SummaryFragmentActivityListener) context;
} catch (ClassCastException cce) {
throw new ClassCastException("class has to implement SummaryFragmentActivityListener");
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_summary, container, false);
Log.d(TAG, "getContext onCreateView es: " + getContext());
expandableIncomesListView = view.findViewById(R.id.expandableIncomingsListView);
expandableExpensesListView = view.findViewById(R.id.expandableExpensesListView);
newIncomeGroupBtn = (Button) view.findViewById(R.id.button_income_group);
newExpenseGroupBtn = (Button) view.findViewById(R.id.button_expense_group);
newIncomeTransactionBtn = (Button) view.findViewById(R.id.button_incoming);
newExpenseTransactionBtn = (Button) view.findViewById(R.id.button_expense_transaction);
// setting list adapter
expandableIncomesListView.setAdapter(listener.getIncomesAdapter());
expandableExpensesListView.setAdapter(listener.getExpensesAdapter());
initListeners();
return view;
}
/**
* method to initialize listeners of the views
*/
private void initListeners() {
expandableExpensesListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
@Override
public void onGroupExpand(int groupPosition) {
//Log.d(TAG, "Group position is: " + groupPosition);
}
});
expandableIncomesListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
//Log.d(TAG, "Group INCOMINGS position is: " + groupPosition + " and Child posititon is: " + childPosition);
Transaction transaction = new Transaction();
transaction.getId();
Bundle bundle = new Bundle();
NewTransactionIncome incomeDialog = new NewTransactionIncome();
incomeDialog.show(getFragmentManager(), "Input new income");
return false;
}
});
expandableExpensesListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
NewTransactionExpense expenseDialog = new NewTransactionExpense();
Bundle bundle = new Bundle();
bundle.putSerializable(NewTransactionExpense.ARG_EXPENSE_ID, ((Transaction) listener.getExpensesAdapter().getChild(groupPosition, childPosition)).getId());
expenseDialog.setArguments(bundle);
expenseDialog.show(getFragmentManager(), "Input new income");
return false;
}
});
/**
* Initialize Button listeners
*/
newIncomeTransactionBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(@Nullable View v) {
NewTransactionIncome incomeDialog = new NewTransactionIncome();
}
});
newIncomeGroupBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NewCategoryIncome categoryIncomeDialog = new NewCategoryIncome();
categoryIncomeDialog.show(getFragmentManager(), "NewIncomeCategory");
}
});
newExpenseTransactionBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(@Nullable View v) {
NewTransactionExpense expenseDialog = new NewTransactionExpense();
Transaction transaction = new Transaction();
Bundle bundle = new Bundle();
bundle.putSerializable(NewTransactionExpense.ARG_EXPENSE_ID, transaction.getId());
//expenseDialog.setArguments(bundle);
expenseDialog.show(getFragmentManager(), "Input new expense");
}
});
newExpenseGroupBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NewCategoryExpense categoryExpenseDialog = new NewCategoryExpense();
categoryExpenseDialog.show(getFragmentManager(), "NewExpenseCategory");
}
});
}
public interface SummaryFragmentActivityListener {
List<Transaction> getTransactionsByTable(String table);
Map<String, List<Transaction>> getExpensesDataChild();
ExpandableListViewAdapter getIncomesAdapter();
ExpandableListViewAdapter getExpensesAdapter();
void updateCategories();
}
}