Programming     Travel Logs     Life Is Good     Surfing Online     About Me
Specific knowledge is often highly technical or creative. It cannot be outsourced or automated.
-Naval Ravikant
2018-07-17 17:47:03

Copy this link when reproducing:
http://www.casperlee.com/en/y/blog/199

When adding spending information, I want to sort them into different categories. So let's initialize the categories of spending first. It is very straightforward, so there is no extra explanations needed, I'll just list all the changes in this article, in stead of explaining too much. Just like before, let's take it easy and enjoy a few beautiful photos first.

/Images/20171002/01.jpg

/Images/20171002/02.jpg

/Images/20171002/03.jpg

/Images/20171002/04.jpg

/Images/20171002/05.jpg

/Images/20171002/06.jpg

1. Open the file "app -> res -> values -> strings.xml", add the following strings into it:

<string name="toast_category_invalid">The Category string is invalid!</string>
    <string name="toast_categories_empty">The Category string is empty!</string>
    <string name="toast_save_categories_failed">Failed to save the Categories!</string>
    <string name="caption_category2"> Input the categories all in one time:</string>
    <string name="caption_category3">" Format: [Name] [Limit] [Name] [Limit] ... (0 represents No Limit)"</string>
    <string name="default_categories">Food 900 Grocery 0 Entertainment 1000 Traffic 150 Communication 100 House 0 Lottery 40</string>
    <string name="btn_ok_caption">OK</string>

2. Open the file "app -> res -> values -> string.xml (zh)", add the following strings into it:

<string name="toast_category_invalid">输入的分类字符串不合法!</string>
    <string name="toast_categories_empty">获取的分类列表为空!</string>
    <string name="toast_save_categories_failed">保存分类信息失败!</string>
    <string name="caption_category2"> 请输入分类信息:</string>
    <string name="caption_category3">" 输入格式为:[名称] [花费目标上限] [名称] [花费目标上限] ... (0代表不设上限)"</string>
    <string name="default_categories">饮食 900 日用 0 娱乐 1000 交通 150 沟通 100 住宿 0 彩票 40</string>
    <string name="btn_ok_caption">确定</string>

3. Open the file "app -> java -> com.casperlee.personalexpense -> dal -> DBHelper", add the following code to create the Category table:

package com.casperlee.personalexpense.dal;
...
import android.util.Log;
...
public class DBHelper extends SQLiteOpenHelper {
    ...
    public static final int DATABASE_VERSION = 3;
    ...
    public DBHelper() {

        super(GlobalVars.MainContext, DatabaseName, null, DATABASE_VERSION);
    }

    ...
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        Log.e(GlobalVars.TraceTag, "OldVersion: " + Integer.toString(oldVersion) + "  newVersion: " + Integer.toString(newVersion) );
        this.createTablesIfNotExists(db);
    }

    private void createTablesIfNotExists(SQLiteDatabase db) {

        ...
        // create category table
        createTable ="create table if not exists "
                + "Category ("
                + "ID INTEGER PRIMARY KEY AUTOINCREMENT,"
                + "Name TEXT,"
                + "LimitMoney REAL,"
                + "T0 TEXT)";
        db.execSQL(createTable);

    }
}

Note: Here I changed the database version from 1 to 3, so that OnUpgrade method will be executed when installing the application. BTW, in my computer, I failed a few times to let OnUpgrade execute. At the end, it turns out it is a problem of my environment. I refresh the project and rebuild it, then the problem is gone.

4. Add a new entity class named "CategoryEntity" in the folder "app -> java -> com.casperlee.personalexpense -> entities", and put the following code in:

package com.casperlee.personalexpense.entities;

public class CategoryEntity {

    private int id;
    private String name;
    private double limit;

    @Override
    public String toString() {

        return this.name;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getLimit() {
        return limit;
    }
    public void setLimit(double limit) {
        this.limit = limit;
    }
}

5. Add a new DAL class named "CategoryDal" in the folder "app -> java -> com.casperlee.personalexpense -> dal", and put the following code in:

package com.casperlee.personalexpense.dal;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.casperlee.personalexpense.entities.CategoryEntity;
import com.casperlee.personalexpense.global.GlobalVars;

public class CategoryDal {

    private SQLiteDatabase db;
    private static CategoryDal instance;

    public CategoryDal() {

        DBHelper helper = DBHelper.getInstance();
        this.db = helper.getWritableDatabase();
    }

    public static CategoryDal getInstance() {

        if (CategoryDal.instance == null) {

            CategoryDal.instance = new CategoryDal();
        }

        return CategoryDal.instance;
    }

    public CategoryEntity[] getAll(boolean aIncludeMenuItem) {

        String sql = "select * from Category";
        Cursor cursor = db.rawQuery(sql, null);
        try {

            if (cursor.getCount() <= 0) {

                return null;

            } else {

                int len = cursor.getCount();
                if (aIncludeMenuItem) {

                    len++;
                }

                CategoryEntity[] es = new CategoryEntity[len];

                cursor.moveToFirst();
                int i = 0;
                while (!cursor.isAfterLast()) {

                    CategoryEntity e = new CategoryEntity();
                    e.setId(cursor.getInt(0));
                    e.setName(cursor.getString(1));
                    e.setLimit(cursor.getDouble(2));
                    es[i++] = e;
                    cursor.moveToNext();
                }

                if (aIncludeMenuItem) {

                    CategoryEntity ee = new CategoryEntity();
                    ee.setId(-1);
                    ee.setName("-+-");
                    ee.setLimit(0);
                    es[es.length - 1] = ee;
                }

                return es;
            }
        } finally {

            cursor.close();
        }
    }
    public CategoryEntity get(int id) {

        String sql = "select * from Category where ID = " + id;
        Cursor cursor = db.rawQuery(sql, null);
        try {

            if (cursor.getCount() <= 0) {

                return null;

            } else {

                // can only find one record.
                cursor.moveToFirst();

                CategoryEntity e = new CategoryEntity();
                e.setId(cursor.getInt(0));
                e.setName(cursor.getString(1));
                e.setLimit(cursor.getDouble(2));
                cursor.moveToNext();

                return e;
            }
        } finally {

            cursor.close();
        }
    }
    public boolean categoryAlreadyUsed(CategoryEntity c) {

        String sql = "select count(*) from Expenditure where Category = " + c.getId();
        Cursor cursor = db.rawQuery(sql, null);
        try {

            if (cursor.getCount() <= 0) {

                return false;

            } else {

                // can only find one record.
                cursor.moveToFirst();

                int count = cursor.getInt(0);
                return count > 0 ? true : false;
            }

        } finally {

            cursor.close();
        }
    }
    public void add(CategoryEntity c) {

        String sql = "insert into Category (Name, LimitMoney, T0) "
                + " values ('" + c.getName() + "'," + c.getLimit() + ",'')";
        db.execSQL(sql);
    }
    public void del(CategoryEntity c) {

        String sql = "delete from Category where ID = " + c.getId();
        db.execSQL(sql);
    }
    public void update(CategoryEntity c) {

        String sql = "update Category"
                + " set Name = '" + c.getName() + "',"
                + " LimitMoney = " + c.getLimit()
                + " where ID = " + c.getId();
        db.execSQL(sql);
    }

    @Override
    protected void finalize() throws Throwable {

        this.db.close();
        super.finalize();
    }
}

6. Open the file "app -> java -> com.casperlee.personalexpense -> global -> GlobalVars", add the following code into it:

package com.casperlee.personalexpense.global;
...
import com.casperlee.personalexpense.entities.CategoryEntity;

public class GlobalVars {

    public static final String TraceTag = "CASPER";
    ...
    public static CategoryEntity[] Categories;
}

7. Add a new BLL class named "CategoryBll" in the folder "app -> java -> com.casperlee.personalexpense -> bll", and put the following code in:

package com.casperlee.personalexpense.bll;

import android.content.Context;
import android.widget.Toast;

import com.casperlee.personalexpense.R;
import com.casperlee.personalexpense.dal.CategoryDal;
import com.casperlee.personalexpense.entities.CategoryEntity;
import com.casperlee.personalexpense.global.GlobalVars;

import java.util.ArrayList;
import java.util.List;

public class CategoryBll {

    private CategoryDal dal;
    private static CategoryBll instance = null;
    public static CategoryBll getInstance() {

        if (instance == null) {

            instance = new CategoryBll();
        }

        return instance;
    }

    public CategoryBll() {

        this.dal = CategoryDal.getInstance();
    }

    public CategoryEntity[] GetCategories(boolean aIncludeMenuItem) {

        return CategoryDal.getInstance().getAll(aIncludeMenuItem);
    }

    public boolean SaveCategories(Context aContext, String aFormattedString) {

        CategoryParser parser = new CategoryParser();
        if (!parser.ParseCategories(aFormattedString)) {

            Toast.makeText(aContext, R.string.toast_category_invalid, Toast.LENGTH_LONG).show();
            return false;
        }

        List<CategoryEntity> items = parser.getItems();
        if (items == null || items.isEmpty()) {

            Toast.makeText(aContext, R.string.toast_categories_empty, Toast.LENGTH_LONG).show();
            return false;
        }

        if (!this.SaveCategories(items)) {

            Toast.makeText(aContext, R.string.toast_save_categories_failed, Toast.LENGTH_LONG).show();
            return false;
        }

        return true;
    }

    private boolean SaveCategories(List<CategoryEntity> anItems) {

        if (anItems == null) {

            return false;
        }

        for (int i = 0; i < anItems.size(); i++) {

            CategoryDal.getInstance().add(anItems.get(i));
        }

        return true;
    }

    private class CategoryParser {

        private List<CategoryEntity> items;
        public List<CategoryEntity> getItems() {

            return this.items;
        }

        public CategoryParser() {
        }

        public boolean ParseCategories(String aFormattedString) {

            String[] strings = aFormattedString.trim().split(" ");
            if (strings == null || strings.length == 0 || strings.length % 2 == 1) {

                return false;
            }

            this.items = new ArrayList<CategoryEntity>();
            CategoryEntity c = null;
            for (int i = 0; i < strings.length; i++) {

                String s = strings[i];
                if (i % 2 == 0) {

                    // Category name.
                    c = new CategoryEntity();
                    c.setId(-1);
                    c.setName(s);

                } else {

                    // Category limit.
                    try {

                        double d = Double.parseDouble(s);
                        c.setLimit(d);
                        this.items.add(c);

                    } catch (NumberFormatException ex) {

                        return false;
                    }
                }
            }

            return true;
        }
    }
}

8. Add a new Activity class named "InitCategoryActivity" in the folder "app -> java -> com.casperlee.personalexpense", and put the following code in:

package com.casperlee.personalexpense;

import com.casperlee.personalexpense.bll.CategoryBll;
import com.casperlee.personalexpense.global.GlobalVars;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class InitCategoryActivity extends AppCompatActivity implements OnClickListener {

    private Layout ui = null;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.setContentView(R.layout.activity_init_category);
        this.ui = new Layout();
        this.ui.btnConfirm.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        String categoryFormattedString = this.ui.etCategory.getText().toString().trim();
        if (CategoryBll.getInstance().SaveCategories(this, categoryFormattedString)) {

            // enter main page
            GlobalVars.Categories = CategoryBll.getInstance().GetCategories(true);
            Toast.makeText(GlobalVars.MainContext, "Success!", Toast.LENGTH_LONG);

            /*
            Intent intent = new Intent();
            intent.setClass(InitCategoryActivity.this, AddExpenseActivity.class);
            this.startActivity(intent);
            this.finish();
            */
        }
    }

    private class Layout {

        private EditText etCategory = null;
        private Button btnConfirm = null;

        public Layout() {

            this.Initialize();
        }

        private void Initialize()
        {
            this.etCategory = (EditText) findViewById(R.id.CategoryEditText);
            this.btnConfirm = (Button) findViewById(R.id.ConfirmButton);
        }
    }
}

Here is the code of its layout ("app -> res -> layout -> activity_init_category.xml"):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_init_category"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:background="#FF000000"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.casperlee.personalexpense.InitCategoryActivity">

    <LinearLayout
        android:id="@+id/CaptionLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFFFF"
            android:text="@string/caption_category2"
            android:textAppearance="?android:attr/textAppearanceLarge" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/EditLayout"
        android:layout_marginTop="6dp"
        android:layout_marginBottom="6dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_below="@+id/CaptionLayout">

        <EditText
            android:id="@+id/CategoryEditText"
            android:layout_width="fill_parent"
            android:layout_height="162dp"
            android:gravity="center_vertical"
            android:inputType="textMultiLine"
            android:textColor="@color/colorWhite"
            android:text="@string/default_categories" >

            <requestFocus />
        </EditText>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/HintLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_below="@+id/EditLayout">

        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFFFF"
            android:text="@string/caption_category3"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ButtonLayout"
        android:layout_marginTop="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_below="@+id/HintLayout">

        <Button
            android:id="@+id/ConfirmButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btn_ok_caption"
            android:textAppearance="?android:attr/textAppearanceMedium" />

    </LinearLayout>

</RelativeLayout>

9. Open the file "app -> java -> com.casperlee.personalexpense -> MainActivity", add the following code into it:

...
import com.casperlee.personalexpense.bll.CategoryBll;
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        GlobalVars.Categories = CategoryBll.getInstance().GetCategories(true);
        if (GlobalVars.Categories == null) {

            Intent intent = new Intent();
            intent.setClass(MainActivity.this, InitCategoryActivity.class);
            this.startActivity(intent);
            this.finish();

        } else {

            /*
            Intent intent = new Intent();
            intent.setClass(MainActivity.this, AddExpenseActivity.class);
            this.startActivity(intent);
            this.finish();
            */
        }
    }

10. Done!