Android Open Source - journal Expense Dialog Fragment






From Project

Back to project page journal.

License

The source code is released under:

MIT License

If you think the Android project journal listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package cochrane343.journal.dialogs;
//from w ww.  j a v  a 2s. c o  m
import static cochrane343.journal.Constants.NO_CATEGORY;
import static cochrane343.journal.Constants.LOGGING_TAG;

import java.math.BigDecimal;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import cochrane343.journal.Constants;
import cochrane343.journal.CurrencyHelper;
import cochrane343.journal.ExpenseEditListener;
import cochrane343.journal.R;
import cochrane343.journal.contentprovider.JournalContract.Category;
import cochrane343.journal.contentprovider.JournalContract.Expense;
import cochrane343.journal.exceptions.IllegalLoaderIdException;
import cochrane343.journal.exceptions.MissingFragmentArgumentException;

/**
 * The dialog for creating new expenses and editing existing ones. <br>
 * Instances of this dialog fragment can be obtained in two ways: <br>
 * <ul>
 * <li>Either by invoking {@link #newInstanceForCreate()} to obtain an empty dialog, which
 * will invoke the callback method {@link ExpenseEditListener#onAddExpense(String, BigDecimal, long) onAddExpense}
 * upon completion.
 * <li>Or by invoking {@link #newInstanceForEdit(long)} to obtain a dialog, which is preset
 * with the current values of the specified expense and will invoke the callback method
 * {@link ExpenseEditListener#onUpdateExpense(long, String, BigDecimal, long) onUpdateExpense}
 * upon completion.
 * </ul>
 * Any <code>Activity</code>, this fragment is attached to, is expected to implement the callback
 * interface {@link ExpenseDialogListener}.
 * 
 * @author cochrane343
 * @since 1.0
 */
public class ExpenseDialogFragment extends DialogFragment
    implements LoaderManager.LoaderCallbacks<Cursor>, OnItemSelectedListener {
    private static final long NO_EXPENSE = -1;

    private static final int LOADER_ID_CATEGORIES = 0;
    private static final int LOADER_ID_EXPENSE = 1;
    
    private final static String BUNDLE_KEY_EXPENSE_ID = "expenseId";
    
    /**
     * The category, which is currently selected in the category spinner
     */
    private long selectedCategory = NO_CATEGORY;
    
    private EditText descriptionEditText;
    private EditText costEditText;
    
    private Spinner categorySpinner;
    private CategorySpinnerAdapter categoryAdapter;

    /* - - - - - Instance Creation - - - - - - - - - - */
    
    /**
     * Required empty public constructor
     */
    public ExpenseDialogFragment() {
        // No-op
    }
    
    /**
     * Creates a new instance of the {@link ExpenseDialogFragment} for creating a new expense.
     */
    public static ExpenseDialogFragment newInstanceForCreate() {
        final ExpenseDialogFragment expenseDialogFragment = new ExpenseDialogFragment();

        final Bundle arguments = new Bundle();
        arguments.putLong(BUNDLE_KEY_EXPENSE_ID, NO_EXPENSE);
        expenseDialogFragment.setArguments(arguments);
        
        return expenseDialogFragment;
    }
    
    /**
     * Creates a new instance of the {@link ExpenseDialogFragment} for editing an existing expense.
     * @param expenseId the id of the expense to be edited by the newly created dialog instance
     */
    public static ExpenseDialogFragment newInstanceForEdit(final long expenseId) {
        final ExpenseDialogFragment expenseDialogFragment = new ExpenseDialogFragment();

        final Bundle arguments = new Bundle();
        arguments.putLong(BUNDLE_KEY_EXPENSE_ID, expenseId);
        expenseDialogFragment.setArguments(arguments);
        
        return expenseDialogFragment;
    }
    
    /* - - - - - Fragment Arguments - - - - - - - - - */
    
    /**
     * @return the id of the expense to be edited by this dialog or {@link Constants#NO_EXPENSE NO_EXPENSE} if
     * this dialog is used to create a new expense
     * @throws MissingFragmentArgumentException if this dialog has no arguments or the respective bundle key
     * is not included in the arguments
     */
    private long getExpenseId() {
        final Bundle arguments = getArguments();
        
        if (arguments == null || !arguments.containsKey(BUNDLE_KEY_EXPENSE_ID)) {
            throw new MissingFragmentArgumentException(BUNDLE_KEY_EXPENSE_ID);
        }
        
        return arguments.getLong(BUNDLE_KEY_EXPENSE_ID);
    }
    
    /* - - - - - Fragment Lifecyle - - - - - - - - - - - - */
    
    @Override
    public Dialog onCreateDialog(final Bundle savedInstanceState) {
        final View view = inflateView();
        
        final TextView currencySymbolTextView = (TextView) view.findViewById(R.id.label_currency_symbol);
        currencySymbolTextView.setText(CurrencyHelper.getCurrencySymbol());
        
        setupCategorySpinner();

        final boolean isCreateDialog = (getExpenseId() == NO_EXPENSE);
        
        final int positiveButtonLabel;
        if (isCreateDialog) {
            positiveButtonLabel = R.string.label_add;
        } else {
            positiveButtonLabel = R.string.label_update;          
        }
        
        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setView(view).setTitle(R.string.label_new_expense)
            .setInverseBackgroundForced( true )
            .setPositiveButton(positiveButtonLabel, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(final DialogInterface dialog, final int which) {
                    // Pass empty handler in order to get the button instantiated
                }
            })
            .setNegativeButton(R.string.label_cancel, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    ExpenseDialogFragment.this.getDialog().cancel();
                }
            });

        return builder.create();
    }

    private View inflateView() {
        final LayoutInflater inflater = getActivity().getLayoutInflater();
        final View view = inflater.inflate(R.layout.new_expense_dialog, null);
        
        descriptionEditText = (EditText) view.findViewById(R.id.new_expense_description);
        costEditText = (EditText) view.findViewById(R.id.new_expense_cost);
        categorySpinner = (Spinner) view.findViewById(R.id.spinner_category);
        
        return view;
    }
    
    private void setupCategorySpinner() {
        categoryAdapter = new CategorySpinnerAdapter(getActivity(), null);
        
        categorySpinner.setAdapter(categoryAdapter);
        categorySpinner.setOnItemSelectedListener(this);
        
        getLoaderManager().initLoader(LOADER_ID_CATEGORIES, null, this);
    }
    
    @Override
    public void onStart() {
        super.onStart();  
        
        final AlertDialog dialog = (AlertDialog) getDialog();
        final Button positiveButton = (Button) dialog.getButton(Dialog.BUTTON_POSITIVE);

        positiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View view) {
                /* - - - - - Read expense description - - - - - */
                
                final String description = descriptionEditText.getText().toString();
                            
                if (description.length() == 0) {
                    Toast.makeText(getActivity(), R.string.toast_enter_description, Toast.LENGTH_SHORT).show();
                    return;
                }
                
                /* - - - - - Read expense cost - - - - - */
                
                final String costInput = costEditText.getText().toString();
                
                if (!CurrencyHelper.isValidInput(costInput)) {
                    if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
                        Log.d(LOGGING_TAG, "User entered an invalid cost: " + costInput);
                    }
                    
                    Toast.makeText(getActivity(), R.string.toast_enter_valid_cost, Toast.LENGTH_SHORT).show();
                    return;
                }
            
                final long costInCents = CurrencyHelper.parseInput(costInput);
          
                
                final long expenseId = getExpenseId();
                final boolean isCreateDialog = (expenseId == NO_EXPENSE);
                
                final ExpenseEditListener listenerActivity = (ExpenseEditListener) getActivity();

                if (isCreateDialog) {
                    if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
                        Log.d(LOGGING_TAG, "Adding new expense '" + description + "' (" + costInCents + ") in category " + selectedCategory);
                    }
                    listenerActivity.onAddExpense(description, costInCents, selectedCategory);                   
                } else {
                    if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
                        Log.d(LOGGING_TAG, "Updating expense " + expenseId + " with '" + description + "' (" + costInCents + ") in category " + selectedCategory);
                    }
                    listenerActivity.onUpdateExpense(expenseId, description, costInCents, selectedCategory);
                }

                dialog.dismiss();
            }
        });
    }
    
    /* - - - - -  Loader Callback - - - - - - - - - - - */
    
    @Override
    public Loader<Cursor> onCreateLoader(final int loaderId, final Bundle args) {
        if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
            Log.d(LOGGING_TAG, "Creating loader for loader id: " + loaderId);
        }
        
        switch (loaderId) {
            case LOADER_ID_CATEGORIES:
                return createCategoriesLoader();
            case LOADER_ID_EXPENSE:
                return createExpenseLoader();
            default:
                throw new IllegalLoaderIdException(loaderId);
        }
    }
    
    /**
     * @return the loader for retrieving the names of all categories to populate
     * the category spinner
     */
    private Loader<Cursor> createCategoriesLoader() {
        final Uri uri = Category.CATEGORIES_URI;
        final String[] projection = new String[]{
                Category._ID,
                Category._NAME };
        final String sortOrder = Category._SORT_ORDER + " ASC";

        return new CursorLoader(getActivity(), uri, projection, null, null, sortOrder);
    }
       
    /**
     * @return the loader for retrieving the description, cost and category of
     * the expense edited in this dialog
     */
    private Loader<Cursor> createExpenseLoader() {
        final long expenseId = getExpenseId();
        final Uri uri = ContentUris.withAppendedId(Expense.EXPENSES_URI, expenseId);
        final String[] projection = new String[]{
                Expense._DESCRIPTION,
                Expense._COST,
                Expense._CATEGORY };
        
        return new CursorLoader(getActivity(), uri, projection, null, null, null);
    }

    /**
     * Callback method invoked when a previously created loader has finished its load.
     * If the finished loader is the categories name loader and this dialog is used to
     * edit an existing expense, this method initialized the loader for loading the info
     * on the edited expense. This is necessary in order to prevent the expense loader 
     * from finishing before the category spinner is fully populated.
     * If the finished loader is the expense loader, this method populates the input fields
     * of this dialog with the current values of the edited expense.
     */
    @Override
    public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
        final int loaderId = loader.getId();
        
        if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
            Log.d(LOGGING_TAG, "Finished load of loader with id: " + loaderId);
        }
        
        switch (loaderId) {
            case LOADER_ID_CATEGORIES:
                categoryAdapter.swapCursor(data);
                
                final boolean isEditDialog = (getExpenseId() != NO_EXPENSE);
                
                if (isEditDialog) {
                    getLoaderManager().initLoader(LOADER_ID_EXPENSE, null, this);
                }
                break;
            case LOADER_ID_EXPENSE:
                if (data.moveToFirst()) {
                    // Set the description edit text
                    final int descriptionIndex = data.getColumnIndex(Expense._DESCRIPTION);
                    final String description = data.getString(descriptionIndex);
                    descriptionEditText.setText(description);
                    
                    // Set the cost edit text      
                    final int costIndex = data.getColumnIndex(Expense._COST);
                    final long costInCents = data.getLong(costIndex);
                    final String costInput = CurrencyHelper.toInputString(costInCents);
                    costEditText.setText(costInput);
                    
                    // Set the category spinner
                    final int categoryIndex = data.getColumnIndex(Expense._CATEGORY);
                    final long categoryId = data.getLong(categoryIndex);
                    selectCategory(categoryId);
                }
                break;
            default:
                throw new IllegalLoaderIdException(loaderId);
        }
    }

    @Override
    public void onLoaderReset(final Loader<Cursor> loader) {
        final int loaderId = loader.getId();
        
        if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
            Log.d(LOGGING_TAG, "Reseting loader with id: " + loaderId);
        }
        
        switch (loaderId) {
            case LOADER_ID_CATEGORIES:
                categoryAdapter.swapCursor(null);
                break;
            case LOADER_ID_EXPENSE:
                // No-op
                break;
            default:
                throw new IllegalLoaderIdException(loaderId);
        }
    }
    
    /* - - - - -  Category Spinner - - - - - - - - - - - */
    
    /**
     * Selects the specified category in the category spinner.
     * @param categoryId the id of the category to select
     */
    private void selectCategory(final long categoryId) {
        for (int i = 0; i < categorySpinner.getCount(); i++) {              
            long itemIdAtPosition = categorySpinner.getItemIdAtPosition(i);
            if (itemIdAtPosition == categoryId) {
                categorySpinner.setSelection(i);
                break;
            }
         };    
    }
    
    /**
     * Callback method invoked when the user selects a category in the category spinner
     */
    @Override
    public void onItemSelected(final AdapterView<?> parent, final View view, 
            final int pos, final long categoryId) {
        if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
            Log.d(LOGGING_TAG, "Selected category " + categoryId);
        }
        
        selectedCategory = categoryId;
    }
    
    /**
     * Callback method invoked when the user selects nothing in the category spinner
     */
    @Override
    public void onNothingSelected(final AdapterView<?> parent) {
        if (Log.isLoggable(LOGGING_TAG, Log.DEBUG)) {
            Log.d(LOGGING_TAG, "Selected no category");
        }
        
        selectedCategory = NO_CATEGORY;
    }
}




Java Source Code List

cochrane343.journal.Constants.java
cochrane343.journal.CurrencyHelper.java
cochrane343.journal.DateTimeHelper.java
cochrane343.journal.ExpenseEditListener.java
cochrane343.journal.ExpensesListAdapter.java
cochrane343.journal.MainActivity.java
cochrane343.journal.MonthlyExpensesFragment.java
cochrane343.journal.MonthlyExpensesPagerAdapter.java
cochrane343.journal.SettingsActivity.java
cochrane343.journal.TranslationHelper.java
cochrane343.journal.contentprovider.JournalContentProvider.java
cochrane343.journal.contentprovider.JournalContract.java
cochrane343.journal.contentprovider.JournalDatabaseHelper.java
cochrane343.journal.dialogs.CategorySpinnerAdapter.java
cochrane343.journal.dialogs.ExpenseDialogFragment.java
cochrane343.journal.dialogs.ExpenseDialogListener.java
cochrane343.journal.exceptions.IllegalDisplayModeException.java
cochrane343.journal.exceptions.IllegalLoaderIdException.java
cochrane343.journal.exceptions.IllegalUriException.java
cochrane343.journal.exceptions.MissingFragmentArgumentException.java