2

I have a Custom ListView this is the ListView I am using that shows some data on the screen, Pretty simple. Now I need to theme the shown data view. the way I do this is by saving a key, value to SQLite adapter, I don't want to use SharedPrefs, This takes a long process to read over 120+ keys! and lags the UI a lot so I thought lets make an async setBackground so here is what I put together.

public static HashMap<String, String> lruCache = new HashMap<>();

I cache all the keys in a hashset

and then I made a method that checks if the key exists if not it gets the key using AsyncTask

public static void setBackgroundColor(View view, String key, String defaultValue) {
    String val = lruCache.get(key);
    if (val != null) {
        view.setBackgroundColor(ThemeUtils.parseColor(val));
        return;
    }
    new AsyncBackgroundColor(view).execute(key, defaultValue);
}

public static class AsyncBackgroundColor extends AsyncTask<String, String, Integer> {
    WeakReference<View> view;

    AsyncBackgroundColor(View view) {
        this.view = new WeakReference<>(view);
    }

    @Override
    protected Integer doInBackground(String... strings) {
        return ThemeUtils.getColor(strings[0], strings[1]);
    }

    @Override
    protected void onPostExecute(Integer color) {
        view.get().setBackgroundColor(color);
    }
}

and this is how my getColor method works.

public static int getColor(String str, String defaultValue) {
    ThemeDatabaseManager lynxDatabaseHelper = new ThemeDatabaseManager(LynxBase.getApplicationContext()).open();
    return ThemeUtils.parseColor(lynxDatabaseHelper.getString(str, defaultValue));
}

It gets the string from my SQlite database and parses it to an int. This is my getString method

public String getString(String key, String defaultValue) {
    String cachedValue = ThemeDatabaseCache.lruCache.get(key);
    if (cachedValue != null) {
        return cachedValue;
    }
    if (!database.isOpen()) open();
    String[] columns = new String[]{ThemeDatabaseHelper.COLUMN_NAME_TITLE, ThemeDatabaseHelper.COLUMN_NAME_SUBTITLE};
    Cursor cursor = database.query(TABLE_NAME, columns, null, null, null, null, null);
    if(cursor != null) {
        cursor.moveToFirst();
        if(cursor.getCount() != 0) {
            do {
                if (!(cursor.getColumnCount() <= 1)) {
                    String k = cursor.getString(cursor.getColumnIndex(ThemeDatabaseHelper.COLUMN_NAME_TITLE));
                    String value = cursor.getString(cursor.getColumnIndex(ThemeDatabaseHelper.COLUMN_NAME_SUBTITLE));
                    if (k.equals(key)) {
                        cursor.close();
                        if (database.isOpen()) database.close();
                        ThemeDatabaseCache.lruCache.put(key, defaultValue);
                        return value;
                    }
                }
            } while (cursor.moveToNext());
        }
        cursor.close();
    }
    insertOrUpdate(key, defaultValue);
    if (database.isOpen()) database.close();
    return defaultValue;
}

I fetch all the SQLite colums and loop until I find the correct key and then return that how ever if the value doesn't exist I just insert the default value into the SQLite database, that way I always end up with a key the other time.

The problem happens here. It doesn't theme all the tabs in the adapter.

enter image description here

as you can see It only themed the 3rd adapter item, But when I scroll up and down, the position changes. So It wont change 3rd its gonna be 5th and you get the point, Does anyone know how I can resolve this? I am been debugging this for about 5 days now, Tried all sorts of stuff can't seem to fix it.

The black is what all of the item is suppose to look like once the setBackgroundColor is done. The white is the default color applied using XML Layout.

This is how I call it on my adapter.

public final View getView(int i, View view, ViewGroup viewGroup){\
    ...
    view = inflate(R.layout.my_view, viewGroup, false);
    setBackground(view);
    ...
}

and my class is extending a custom class that I made that extends BaseAdapter if that helps!

This is what I have tried according to the answer.

public static void setBackgroundColor(BaseAdapter baseAdapter, View view, String key, String defaultValue) {
    String val = lruCache.get(key);
    if (val != null) {
        Log.wtf("Lynx", "background set using cached Color.");
        view.setBackgroundColor(ThemeUtils.parseColor(val));
        baseAdapter.notifyDataSetChanged();
        return;
    }
    new AsyncBackgroundColor(baseAdapter, view).execute(key, defaultValue);
}

..

public static class AsyncBackgroundColor extends AsyncTask<String, String, Integer> {
    WeakReference<View> view;
    BaseAdapter baseAdapter;

    AsyncBackgroundColor(BaseAdapter baseAdapter, View view) {
        this.view = new WeakReference<>(view);
        this.baseAdapter = baseAdapter;
    }

    @Override
    protected Integer doInBackground(String... strings) {

        return ThemeUtils.getColor(strings[0], strings[1]);
    }

    @Override
    protected void onPostExecute(Integer color) {
        Log.wtf("Lynx", "background set using async task.");
        view.get().setBackgroundColor(color);
        if(baseAdapter != null)
        baseAdapter.notifyDataSetChanged();
    }
}

But It's still the same as before.

Here is the Catlog dump:

enter image description here

SamHoque
  • 2,978
  • 2
  • 13
  • 43
  • Can you confirm that `getColor` gets called for all your items and returns the correct result? And the problem is just that the color returned is not reflected on the view ? – 113408 Feb 17 '19 at 19:45
  • @113408 it gets called on all my items, Because I call it inside my recylerView where is set all the other objects. This works if I just set the color from the database and skip the async bit. But when I use async it doesn't work. – SamHoque Feb 17 '19 at 19:46
  • @113408 I will do some debugging and post the debug log on the post. – SamHoque Feb 17 '19 at 19:48
  • If I understand well, `setBackgroundColor` is a method within your `CustomAdapter` then why you pass it as an argument? I would use `this` to refer it and pass it to the `AsyncTask` – 113408 Feb 17 '19 at 20:51
  • @113408 No It's not, Its a method in an outterclass. – SamHoque Feb 17 '19 at 20:52

2 Answers2

1

This works if I just set the color from the database and skip the async bit. But when I use async it doesn't work.

This happens because your AsyncTask doesn't work on the UIThread and therefore when the result is back it doesn't redraw your items. To do so you need to notify the adapter that new elements are present using myAdapter.notifyDataSetChanged();

From the code you shared, I guess you can just call it from your postExecute:

public static class AsyncBackgroundColor extends AsyncTask<String, String, Integer> {
    WeakReference<View> view;
    WeakReference<Adapter> adapter;

    AsyncBackgroundColor(Adapter ad, View view) {
        this.view = new WeakReference<>(view);
        this.adapter = new WeakReference<>(ad);
    }

    @Override
    protected Integer doInBackground(String... strings) {
        return 1;
    }

    @Override
    protected void onPostExecute(Integer color) {
        view.get().setBackgroundColor(color);
        adapter.get().notifyDataSetChanged();
    }
}

Also on your getString() change this part:

if (k.equals(key)) {
                    cursor.close();
                    if (database.isOpen()) database.close();
                    ThemeDatabaseCache.lruCache.put(key, value);
                    return value;
                }
113408
  • 3,364
  • 6
  • 27
  • 54
  • I actually can't access my `recyclerView` I only have access to the view objects. I can't update the adapter nor the layoutManager. – SamHoque Feb 17 '19 at 19:55
  • and if I have over 100 items, Its gonna call that piece of code 100 times, I don't see a way my code isn't called on UIThread either since it's called on `onPostExecute`? – SamHoque Feb 17 '19 at 19:56
  • @SamzSakerz the purpose of using `AsyncTask` as you mentioned is to not block your UI while loading the stuff from cache/DB. So when the result is back `onCreateViewHolder` is not called again and therefore your color doesn't get applied. – 113408 Feb 17 '19 at 20:00
  • Well my issue is I can't access the recylerView ref, Since the workspace I am working on is limited, I only have access to the `getView` on the adapter – SamHoque Feb 17 '19 at 20:02
  • Oops, I actually made a mistake on the question I am not actually using a recylerview I forgot I switched my code to https://github.com/ksoichiro/Android-ObservableScrollView/blob/master/library/src/main/java/com/github/ksoichiro/android/observablescrollview/ObservableListView.java, So Its actually a ListVIew. I will update the question – SamHoque Feb 17 '19 at 20:04
  • I updated the question but yeah, I can't access the ListView reference, Since I don't want to pass it to my adapter. – SamHoque Feb 17 '19 at 20:06
  • @SamzSakerz have you tried `myAdapter.notifyDataSetChanged();` on the `setBackgroundColor` ? – 113408 Feb 17 '19 at 20:14
  • No I did not, Since I am only passing the view. I don't think I am allowed to pass the adapter. – SamHoque Feb 17 '19 at 20:16
  • Are you able to share the code on how you setup the adapter and how you call setBackgroundColor ? if so please update the question – 113408 Feb 17 '19 at 20:20
  • I am afraid I can't show you how I setup the adapter. but basically this is what I do for the `setBackgroundColor` part. I just call it on my custom layout on the `getView` method. – SamHoque Feb 17 '19 at 20:21
  • Updated the question, showing how I call the setBackground – SamHoque Feb 17 '19 at 20:23
  • I am gonna try to pass `this` to my setBackground method as `BaseAdapter` and try to call `notifyDatasetChanged` inside `onPostExecute` and let you know how it goes. – SamHoque Feb 17 '19 at 20:30
  • Hi, I tried what you suggested just now, and it didnt work. – SamHoque Feb 17 '19 at 20:42
  • Updated post. . – SamHoque Feb 17 '19 at 20:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188555/discussion-between-113408-and-samzsakerz). – 113408 Feb 17 '19 at 20:52
1

Just as a complement. Instead of fetching your whole database and match ThemeDatabaseHelper.COLUMN_NAME_TITLE, you should change your query to only get the entries you are interested in. It would speed up your operations and avoid delays.

Yuenbe
  • 46
  • 5
  • I am not sure how to do that, I am still new to the SQLite api, can you provide an example? – SamHoque Feb 22 '19 at 04:04
  • Sorry for the late reply. I haven't tested but this should work. Replace `Cursor cursor = database.query(TABLE_NAME, columns, null, null, null, null, null);` with `Cursor cursor = database.query(TABLE_NAME, columns, ThemeDatabaseHelper.COLUMN_NAME_TITLE, key, null, null, null);`. Basically you are telling the database to only get the entries with a title that matches `key`. – Yuenbe Feb 24 '19 at 18:01
  • I see. I am guessing I still need the loop? – SamHoque Feb 24 '19 at 21:02
  • Actually you probably won't need it anymore. – Yuenbe Feb 25 '19 at 16:11
  • Hmm, Will take a look thanks. Upvoted for your efforts. – SamHoque Feb 26 '19 at 13:19