vendredi 26 juin 2015

Android sqlite cashing mechanism

Good day, I am developing sql cashing mechanism for my news feed application and I have some troubles with realization.

I am working with RecyclerView (ListView) and I created class "DataHelper" in witch I have method "getNode":

public NewsNode getNode(int id, int hash, String tableName, DbHelper.NodeLoadCallback callback) {
    nodeCallback = callback;
    SQLiteDatabase db = this.getWritableDatabase();
    Cursor c = db.rawQuery("Select * FROM " + newsTableName + " WHERE id = '" + Integer.toString(id) +"';", null);
    if (c.moveToFirst()) {
        // exist in db
        int hashColIndex = c.getColumnIndex("hash");
        if (hash == c.getInt(hashColIndex)) {
            // dont need change
            return new NewsNode(c.getInt(c.getColumnIndex("id")), c.getInt(c.getColumnIndex("hash")), c.getString(c.getColumnIndex("title")), c.getString(c.getColumnIndex("description")), c.getString(c.getColumnIndex("image")));
        } else {
            new LoadNodeAsync().execute();
            return null;
        }
    } else {
        new LoadNodeAsync().execute();
        return null;
    }
}

private class LoadNodeAsync extends AsyncTask<Integer, Void, NewsNode> {


    @Override
    protected void onPreExecute() { }

    @Override
    protected NewsNode doInBackground(Integer... id) {
        JSONObject json = JsonParser.downloadJson(getNewsNodeUrl); // по API сгенерировать id getter
        if (json != null) {
            try {
                JSONObject node = json.getJSONObject("node");
                DbHelper dbHelper = new DbHelper(context);
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues args = new ContentValues();
                args.put("id", node.getInt("id"));
                args.put("hash", node.getInt("hash"));
                args.put("title", node.getString("title"));
                args.put("description", node.getString("description"));
                args.put("image", WorkWithImage.saveToInternalStorage(WorkWithImage.downloadImage(node.getString("image")), newsTableName + id.toString(), context)); // !!!
                Cursor c = db.rawQuery("Select * FROM " + newsTableName + " WHERE id = '" + id.toString() + "';", null);
                if (c.moveToFirst()) {
                    // need change row
                    db.update(newsTableName, args, "id" + "=" + id.toString(), null);
                } else {
                    // need create new row in db
                    db.insert(newsTableName, null, args);
                }
                return new NewsNode(node.getInt("id"), node.getInt("hash"), node.getString("title"), node.getString("description"), newsTableName + id.toString()); // link to image
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    protected void onPostExecute(NewsNode node) {
        if (node != null) {
            nodeCallback.nodeLoadCallback(node);
        } else {
            // error
        }
    }
}

It is called in RecyclerView.Adapter -> onBindViewHolder method, i.e. every time, when user scrolled to specific node in my list:

public void onBindViewHolder(ViewHolder holder, int position) {
        if (this.nodes.get(position).isNeedLoad()) {
            NewsNode node = dbHelper.getNode(this.nodes.get(position).getId(),this.nodes.get(position).getHash(), DbHelper.newsTableName, activity);
            // еif not null => load from cash, else handle result in callback method
            if (node != null) {
                holder.title.setText(node.getTitle());
                holder.description.setText(node.getDescription());
                holder.image.setImageBitmap(WorkWithImage.loadImageFromStorage(node.getImage()));
            }
        } else {
            holder.title.setText(this.nodes.get(position).getTitle());
            holder.description.setText(this.nodes.get(position).getDescription());
            holder.image.setImageBitmap(WorkWithImage.loadImageFromStorage(this.nodes.get(position).getImage()));
        }
}

In my "getNode" method I checked first if there is a local copy of node and if it doesn't need to be changed (via hash condition), then I just go to my sqlite db and return value, thats okay. But if I need to update data, I have to start async task and load data from internet. So the question is: how can I handle result in my ListView? I can't return download value from "getNode" method, bcs I am using another Thread and don't want to freeze UI. All what I thought at the moment is to create callback interface and when data is loaded notify adapter about changes:

public void nodeLoadCallback(NewsNode callbackNode) {
    for (int i = 0; i< nodes.size(); i++) {
        if (nodes.get(i).getId() == callbackNode.getId()) {
            nodes.set(i, callbackNode);
        }
    }
    mAdapter.notifyDataSetChanged(); // ??? reload all ??
}

But in my opinion it is too rough. I am hoping some1 understand my problem and will help with some tips. Thx!!

Aucun commentaire:

Enregistrer un commentaire