2

I have a Fragment containing ListView. I'm adding some values to the database with a dialog and I want to update this ListView after dialog is dismissed. Also, when I change the tab, the ListView is not updated but when the application is turned on and off, the ListView is updated.

Fragment and Dialog classes are as follows:

Fragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.fragment_teams, container, false);

    listViewTeams = rootView.findViewById(R.id.listView_teams);

    TeamDatabase teamDatabase = new TeamDatabase(getContext());
    teamDatabase.open();
    arrayListTeam = teamDatabase.getAllTeams();
    teamDatabase.close();

    int resID = R.layout.team_list_item;
    teamListArrayAdapter = new TeamListArrayAdapter(getContext(), resID, arrayListTeam);
    listViewTeams.setAdapter(teamListArrayAdapter);

    return rootView;
}

Dialog onClick Method:

@Override
public void onClick(View view) {
   int id = view.getId();

   switch (id){
       case R.id.button_alertDialogAddTeam_cancel:
           this.dismiss();
           break;
       case R.id.button_alertDialogAddTeam_ok:
           Team team = new Team();
           team.setName(editTextTeamName.getText().toString());
           team.setCode(editTextTeamCode.getText().toString());

           TeamDatabase teamDatabase = new TeamDatabase(getContext());
           teamDatabase.open();
           if(teamDatabase.addNewTeam(team)) {
               Toast.makeText(getContext(), team.getCode() + " - " +
                       team.getName() + " was added successfully", Toast.LENGTH_SHORT).show();
           }

           this.dismiss();
           break;
   }
}

TeamDatabase class:

    public static final String TABLE_NAME = "team";

private static final String KEY_ID = "id";
private static final String KEY_NAME = "name";
private static final String KEY_CODE = "code";
private static final String KEY_EMBLEM = "emblem";

private Context context;

public static final String CREATE_TABLE = "CREATE TABLE "+
        TABLE_NAME + " ("+
        KEY_ID  + " INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, "+
        KEY_NAME + " TEXT NOT NULL, "+
        KEY_CODE + " TEXT NOT NULL, " +
        KEY_EMBLEM + " TEXT);";


public TeamDatabase(Context context) {
    super(context);
    this.context = context;
}

public boolean addNewTeam(Team team){
    ContentValues contentValues = new ContentValues();

    contentValues.put(KEY_NAME, team.getName());
    contentValues.put(KEY_CODE, team.getCode());

    return db.insert(TABLE_NAME, null, contentValues) > 0;
}

public ArrayList<Team> getAllTeams()
{
    ArrayList<Team> teams = new ArrayList<Team>();

    Cursor cursor = db.query(TABLE_NAME, new String[]{KEY_ID,
            KEY_NAME,
            KEY_CODE}, null, null, null, null, null);

    while(cursor.moveToNext()) {

        Team team = new Team();

        team.setId(cursor.getInt(cursor.getColumnIndex(KEY_ID)));
        team.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
        team.setCode(cursor.getString(cursor.getColumnIndex(KEY_CODE)));

        teams.add(team);
    }
    return teams;
}

DatabaseHelper class:

private static final String DATABASE_NAME = "fixtureCreator.db";
private static final int DATABASE_VERSION = 1;

public SQLiteDatabase db;

public DatabaseHelper(Context context){
    super(context, DATABASE_NAME, null, DATABASE_VERSION);

}

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
    sqLiteDatabase.execSQL(TeamDatabase.CREATE_TABLE);
}



@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersinon) {
    Log.w("TaskDBAdapter", "Upgrading from version " +
            oldVersion + " to " + newVersinon + ", which will destroy all old data");

    sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + TeamDatabase.TABLE_NAME);
    onCreate(sqLiteDatabase);
}

public DatabaseHelper open() throws SQLException {
    try{
        db = this.getWritableDatabase();
    }catch (SQLException e){
        db = this.getReadableDatabase();
    }
    return this;
}

public void close(){
    db.close();
}
Wooz12345
  • 159
  • 10

4 Answers4

2

On clicking the item in your first Activity, start your second Activity with startActivityForResult()

And then in Second Activity, after dismissing the dialog,in onClick of that button call,

intent.putExtra("new data", "item text");
setResult(RESULT_OK, intent);
finish();

Now you come back to your first Activity and here you have to implement onActivityResult() callback.

You can extract data from that intent's extras and set that respective item in your array and call notifyDataSetChanged().

This is ideally how you should be doing it.

Ouail Bellal
  • 1,554
  • 13
  • 27
  • I use fragment and dialog not an activity. Fragment is not updated even if tab is changed. I have to restart app to update fragment. – Wooz12345 Feb 24 '18 at 18:04
  • @wooz , onActivityResult() will be on your main activity , then call framgentInstance.updateUI() , the updateUI() method must have the refresh code – Ouail Bellal Feb 25 '18 at 19:40
1

You should call notifyDataSetChanged just before dismissing the dialoge

teamListArrayAdapter.notifyDataSetChanged();

You should change you code something like this below

   @Override
public void onClick(View view) {
   int id = view.getId();

   switch (id){
       case R.id.button_alertDialogAddTeam_cancel:
           this.dismiss();
           break;
       case R.id.button_alertDialogAddTeam_ok:
           Team team = new Team();
           team.setName(editTextTeamName.getText().toString());
           team.setCode(editTextTeamCode.getText().toString());

           TeamDatabase teamDatabase = new TeamDatabase(getContext());
           teamDatabase.open();
           if(teamDatabase.addNewTeam(team)) {
               Toast.makeText(getContext(), team.getCode() + " - " +
                       team.getName() + " was added successfully", Toast.LENGTH_SHORT).show();
           }
           arrayListTeam = teamDatabase.getAllTeams();
           teamListArrayAdapter.notifyDataSetChanged();
           this.dismiss();
           break;
   }
}
lib4backer
  • 3,337
  • 3
  • 18
  • 16
  • This will not work if the cursor is not being populated again manually after each `onClick`. This is not the correct answer. – Reaz Murshed Feb 24 '18 at 17:46
  • have you tried getting arrayListTeam = teamDatabase.getAllTeams(); again in onClick? see the updated answer – lib4backer Feb 24 '18 at 17:49
  • Now you have edited the answer, which should work. However, there are elegant ways to solve this problem using content observer. I am writing an answer for this. – Reaz Murshed Feb 24 '18 at 17:50
  • yes I have tried get datas from database and call notifyDataSetChanged() method. As I mentioned in the question listView is not updated when I change tab. – Wooz12345 Feb 24 '18 at 17:52
  • Cool, lots of ways can solve this issue, having observable, room livedata, but I was just idenfying the issue in the code posted. As he didnt ask for any better methods to resolve , this. Rather was asking for a specific question. @ReazMurshed – lib4backer Feb 24 '18 at 17:52
  • @wooz did u implemented addOnTabSelectedListener ? something like this https://stackoverflow.com/questions/43045672/tab-change-listener-android – lib4backer Feb 24 '18 at 17:54
1

The problem can be solved using fetching the data after saving it each time you click on the Dialog and then call notifyDataSetChanged() on your adapter. However, there is more elegant way of achieving this kind of behaviour, which will solve both of your problems using content observer.

In case of having a content observer in your database table you need to declare a URI first which references your table.

public static final Uri DB_TABLE_TEAM_URI = Uri
            .parse("sqlite://" + Constants.ApplicationPackage + "/" + DB_TABLE_TEAM);

// DB_TABLE_TEAM refers to the database table that you have for storing teams.

Now in your addNewTeam function you need to do the following.

public boolean addNewTeam(Team team) {
    // .. Save the team in database
    // Notify the observer about the change in the content
    context.getContentResolver().notifyChange(DBConstants.DB_TABLE_TEAM_URI, null);
}

You need to call notifyChange() function whenever you add or update an entry in your team table.

Now in your Activity or Fragment you need to register your observer on your cursor having the team data fetched from your team table.

cursor = teamDatabase.getAllTeamsInCursor();
this.registerContentObserver(cursor, DBConstants.DB_TABLE_TEAM_URI);

Now populate your ListView using the cursor by passing it to your adapter of ListView. The list will be updated automatically once a new data has been inserted in your team table.

Update

Modify the addNewTeam function in your TeamDatabase class as follows.

public static final Uri DB_TABLE_TEAM_URI = Uri
        .parse("sqlite://" + Constants.ApplicationPackage + "/" + DB_TABLE_TEAM);

public boolean addNewTeam(Team team){
    ContentValues contentValues = new ContentValues();

    contentValues.put(KEY_NAME, team.getName());
    contentValues.put(KEY_CODE, team.getCode());

    boolean success = db.insert(TABLE_NAME, null, contentValues) > 0;
    context.getContentResolver().notifyChange(DBConstants.DB_TABLE_TEAM_URI, null);
    return success;
}

To implement the functionalities with LoaderCallbacks you first need to implement the interface of LoaderCallbacks in your Fragment. So the declaration of your Fragment will look like.

public class TeamsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
    // .... Code
}

Now you need to override the functions that comes along with the interface of LoaderCallbacks.

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    return new SQLiteCursorLoader(getActivity()) {
        @Override
        public Cursor loadInBackground() {
            // Initialize your database
            TeamDatabase teamDatabase = new TeamDatabase(getActivity());

            Cursor cursor = teamDatabase.getAllTeams();

            if (cursor != null) {
                // Register the content observer here
                this.registerContentObserver(cursor, DBConstants.DB_TABLE_TEAM_URI);
            }

            return cursor;
        }
    };
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Set the cursor in your adapter. Handle null values in your setCursor function in your adapter. The cursor might return null when the table is empty.
    teamAdapter.setCursor(cursor);
    teamAdapter.notifyDataSetChanged();
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
}

Now in your onCreateView function in the Fragment, you need to initiate the loader to fetch data from the table.

getLoaderManager().initLoader(0, null, this).forceLoad();

And destroy the loader and un-register the receiver in the onDestroyView function.

@Override
public void onDestroyView() {
    getLoaderManager().destroyLoader(0);
    super.onDestroyView();
}

I was missing the SQLiteCursorLoader class to be added here.

package com.wooz.observer.databases;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;

public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
    private final ForceLoadContentObserver mObserver;

    private Uri mUri;
    private String[] mProjection;
    private String mSelection;
    private String[] mSelectionArgs;
    private String mSortOrder;

    private Cursor mCursor;

    /* Runs on a worker thread */
    @Override
    public abstract Cursor loadInBackground();

    /**
     * Registers an observer to get notifications from the content provider
     * when the cursor needs to be refreshed.
     */
    public void registerContentObserver(Cursor cursor, Uri observerUri) {
        cursor.registerContentObserver(mObserver);
        cursor.setNotificationUri(getContext().getContentResolver(), observerUri);
    }

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        try {
            if (isReset()) {
                // An async query came in while the loader is stopped
                if (cursor != null) {
                    cursor.close();
                }
                return;
            }
            Cursor oldCursor = mCursor;
            mCursor = cursor;

            if (isStarted()) {
                super.deliverResult(cursor);
            }

            if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
                oldCursor.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates an empty unspecified CursorLoader.  You must follow this with
     * calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc
     * to specify the query to perform.
     */
    public SQLiteCursorLoader(Context context) {
        super(context);
        mObserver = new ForceLoadContentObserver();
    }

    /**
     * Creates a fully-specified CursorLoader.  See
     * {@link ContentResolver#query(Uri, String[], String, String[], String)
     * ContentResolver.query()} for documentation on the meaning of the
     * parameters.  These will be passed as-is to that call.
     */
    public SQLiteCursorLoader(Context context, Uri uri, String[] projection, String selection,
                              String[] selectionArgs, String sortOrder) {
        super(context);
        mObserver = new ForceLoadContentObserver();
        mUri = uri;
        mProjection = projection;
        mSelection = selection;
        mSelectionArgs = selectionArgs;
        mSortOrder = sortOrder;
    }

    /**
     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
     * will be called on the UI thread. If a previous load has been completed and is still valid
     * the result may be passed to the callbacks immediately.
     * <p>
     * Must be called from the UI thread
     */
    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }

    public Uri getUri() {
        return mUri;
    }

    public void setUri(Uri uri) {
        mUri = uri;
    }

    public String[] getProjection() {
        return mProjection;
    }

    public void setProjection(String[] projection) {
        mProjection = projection;
    }

    public String getSelection() {
        return mSelection;
    }

    public void setSelection(String selection) {
        mSelection = selection;
    }

    public String[] getSelectionArgs() {
        return mSelectionArgs;
    }

    public void setSelectionArgs(String[] selectionArgs) {
        mSelectionArgs = selectionArgs;
    }

    public String getSortOrder() {
        return mSortOrder;
    }

    public void setSortOrder(String sortOrder) {
        mSortOrder = sortOrder;
    }

    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        super.dump(prefix, fd, writer, args);
        writer.print(prefix);
        writer.print("mUri=");
        writer.println(mUri);
        writer.print(prefix);
        writer.print("mProjection=");
        writer.println(Arrays.toString(mProjection));
        writer.print(prefix);
        writer.print("mSelection=");
        writer.println(mSelection);
        writer.print(prefix);
        writer.print("mSelectionArgs=");
        writer.println(Arrays.toString(mSelectionArgs));
        writer.print(prefix);
        writer.print("mSortOrder=");
        writer.println(mSortOrder);
        writer.print(prefix);
        writer.print("mCursor=");
        writer.println(mCursor);
        //writer.print(prefix); writer.print("mContentChanged="); writer.println(mContentChanged);
    }

    private class CursorLoaderContentObserver extends ContentObserver {
        public CursorLoaderContentObserver() {
            super(new Handler());
        }

        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) {
            onContentChanged();
        }
    }
} 
Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
  • I think this will work for me. But I need more clarification because I have not used LoaderCallsBack before. I tried to add Uri to my database, but I got a few errors. Can you help me if I add my database codes? – Wooz12345 Feb 24 '18 at 18:26
  • Please see the updated code. I have not tested it. But it should work. – Reaz Murshed Feb 24 '18 at 18:45
  • Thanks ranz I could not implement loaderCallBacks to my but I will do some research on this topic and I will tell you what the result is. – Wooz12345 Feb 24 '18 at 18:59
  • Sure thing! Please let me know if you face any other difficulties. – Reaz Murshed Feb 24 '18 at 18:59
0

Add this code in onCreateView:

teamListArrayAdapter.notifyDataSetChanged();
armen
  • 453
  • 1
  • 4
  • 11