Problem in positioning two row layouts one below each other with multiView RecyclerView? Screenshot included

Viewed 316

Here I use two viewHolders one for NoteViewHolder and another one for ChecklistViewHolder for a single adapter. I have two Lists with different data types in the recyclerView Adapter. I use SQLite Database and created two tables, one for notes and another one for checklists because I use different views for each row layout in the recyclerView. What I want is positioning notes and checklists according how they added. That is what I get in RecyclerView.enter image description here

Here if I add another note, it goes to note section for the yellow part, but I want it to go to after checklist for purple part.

I know how to bind these two views, I don't have problem with binding. My problem is with thee layout.

Adapter class

    public class AdapterClass extends RecyclerView.Adapter {

    List<NoteHelper> notesList;
    List<ChecklistHelper> checklistsList;
    public static final int LAYOUT_ONE = 0;
    public static final int LAYOUT_TWO = 1;


    public AdapterClass(List<NoteHelper> notesList, List<ChecklistHelper> checklistHelperList) {
        this.notesList = notesList;
        this.checklistsList = checklistHelperList;
    }


    @Override
    public int getItemViewType(int position) {
        if (position<notesList.size())
            return LAYOUT_ONE;

       else return  LAYOUT_TWO;


    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        if(viewType==LAYOUT_ONE){
            
            View view = layoutInflater.inflate(R.layout.layout_note, parent,false);
            NoteViewHolder noteViewHolder = new NoteViewHolder(view);
            return noteViewHolder;
        }

        if(viewType==LAYOUT_TWO){
            View view = layoutInflater.inflate(R.layout.layout_checklist, parent, false);
            return new ChecklistViewHolder(view);
        }
        return null;
    }
    
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    }
    
    @Override
    public int getItemCount() {
        return notesList.size() + checklistsList.size();
    }

    class NoteViewHolder extends RecyclerView.ViewHolder{
        public NoteViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    class ChecklistViewHolder extends RecyclerView.ViewHolder{
        public ChecklistViewHolder(@NonNull View itemView) {
            super(itemView);
        }
   }}

Activity class

public class MainActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    Button btnNote, btnChecklist;
    List<NoteHelper> listNotes;
    List<ChecklistHelper> checklistHelperList;
    DbHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recycler_view);
        btnNote = findViewById(R.id.btn_add_note);
        btnChecklist = findViewById(R.id.btn_add_checklist);
        recyclerView = findViewById(R.id.recycler_view);
        dbHelper = new DbHelper(this);

        listNotes = dbHelper.getNotes();
        checklistHelperList = dbHelper.getChecklists();
        
        AdapterClass adapterClass = new AdapterClass(listNotes, checklistHelperList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapterClass);
        listNotes = new ArrayList<>();

        btnNote.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this,activity_add_note.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);

            }
        });

        btnChecklist.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, activity_add_checklist.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);

            }
        });
    }}
public class activity_add_note extends AppCompatActivity {
Button btnSaveNote;
EditText etNote;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add_note);

    btnSaveNote = findViewById(R.id.btn_save_note);
    etNote = findViewById(R.id.et_note);

    btnSaveNote.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            DbHelper  dbHelper = new DbHelper(getApplicationContext());
            dbHelper.getWritableDatabase();
            String note = etNote.getText().toString();
            dbHelper.insertNote(note);

            Intent intent = new Intent(getApplicationContext(), MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
        }
    });
}}

    public class activity_add_checklist extends AppCompatActivity {
    Button btnSaveChecklist;
    EditText etChecklist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_checklist);

        btnSaveChecklist = findViewById(R.id.btn_save_checklist);
        etChecklist = findViewById(R.id.et_checklist);
        btnSaveChecklist.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String checklist = etChecklist.getText().toString();
                DbHelper dbHelper = new DbHelper(getApplicationContext());
                dbHelper.insertChecklist(checklist);

                Intent intent = new Intent(getApplicationContext(), MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
            }
        });
    }
}
1 Answers

Modified your code and made some improvements so that it gives us the expected results. Let's see what's changed and why step by step.

Item Design

The goal here is to combine two different classes into the one list. To make this possible we need to add a BaseItem class which will contain the all common properties and functions of the NoteHelper and ChecklistHelper classes so that we extend them from the BaseItem class.Note that it has an abstract method called getViewType so that the inherited classes can implement this method and provide us their view types.

BaseItem.java

public abstract class BaseItem {

    int id;
    String content;
    long date;

    public BaseItem() {
        content = "";
    }

    public BaseItem(int id, String content, long date) {
        this.id = id;
        this.content = content;
        this.date = date;
    }

    public abstract int getViewType();

    public int getId() {
        return id;
    }

    public String getContent() {
        return content;
    }
}

Now if we see the NoteHelper and ChecklistHelper classes; each class has its own definition of view type and implemets the getViewType class which we will use it for our convenience in the adaptor class.

NoteHelper.java

public class NoteHelper extends BaseItem {

    public static final int VIEW_TYPE = 0;


    public NoteHelper() {
    }

    public NoteHelper(int id, String content, long date) {
        super(id, content, date);
    }


    @Override
    public int getViewType() {
        return VIEW_TYPE;
    }
}

ChecklistHelper.java

public class ChecklistHelper extends BaseItem {

    public static final int VIEW_TYPE = 1;

    public ChecklistHelper(){

    }

    public ChecklistHelper(int id, String content, long date){
        super(id, content, date);
    }

    @Override
    public int getViewType() {
        return VIEW_TYPE;
    }

}

Combinating Two Items Into One List

The next step is design the combination logic. As an example I designed the combination based on creation date. But it can be done based on any criteria similar to this. In order to implement the date based combination logic we will need a long type comparator since we save the creation date as a time stamp using System.currentMillis().

ItemLongDateComparator.java

public class ItemLongDateComparator implements Comparator<BaseItem> {
    @Override
    public int compare(BaseItem baseItem, BaseItem t1) {
        return Long.compare(baseItem.date, t1.date);
    }
}

Redesigning the Databese Helper

Making database queries within the main thread is not a good practice since it can cause UI glitches as the data gets bigger. To prevent UI issues we execute the queries in a background thread using an executor service and deliver the result to the main thread. But before we deliver the result we combine the data in background thread by the creation timestamp criteria, then we send the result to main thread using an interface.

DbHelper.java

public class DbHelper extends SQLiteOpenHelper {

    // This background executor is only for database operations
    private final Executor dbexecutor;

    public static final String DATABASE_NAME = "NOTES.DB";
    public static final int DATABASE_VERSION = 1;

    public static final String NOTES_TABLE = "NOTES_TABLE";
    public static final String CHECKLIST_TABLE = "CHECKLIST_TABLE";

    public static final String COLUMN_ID = "ID";
    public static final String COLUMN_CONTENT = "CONTENT";
    public static final String COLUMN_DATE = "DATE";


    public DbHelper(@Nullable Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        dbexecutor = Executors.newSingleThreadExecutor();
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {

        String query = " CREATE TABLE " + NOTES_TABLE + " ( " + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT ,"  +
                COLUMN_CONTENT + " TEXT, " + COLUMN_DATE + " DATE "  + " ) ";
            sqLiteDatabase.execSQL(query);

        String query2 = "CREATE TABLE " + CHECKLIST_TABLE + " ( " + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                COLUMN_CONTENT + " TEXT, " + COLUMN_DATE + " DATE " + " ) ";
            sqLiteDatabase.execSQL(query2);
    }

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

        if (oldVersion>newVersion) {

            sqLiteDatabase.execSQL(" DROP TABLE IF EXISTS " + NOTES_TABLE);
            sqLiteDatabase.execSQL(" DROP TABLE IF EXISTS " + CHECKLIST_TABLE);
        }
             onCreate(sqLiteDatabase);

    }


    public void insertNote(String content, long date){
        dbexecutor.execute(()-> {
            SQLiteDatabase sqLiteDatabase;
            sqLiteDatabase = getWritableDatabase();

            ContentValues c = new ContentValues();
            c.put(COLUMN_CONTENT, content);
            c.put(COLUMN_DATE, date);
            sqLiteDatabase.insert(NOTES_TABLE, null, c);
        });
    }

    public void insertChecklist(String content, long date){
        dbexecutor.execute(()-> {
            SQLiteDatabase sqLiteDatabase;
            sqLiteDatabase = getWritableDatabase();

            ContentValues c = new ContentValues();
            c.put(COLUMN_CONTENT, content);
            c.put(COLUMN_DATE, date);
            sqLiteDatabase.insert(CHECKLIST_TABLE, null, c);
        });
    }


    private List<NoteHelper>  getNotes() {
        List<NoteHelper> notesList = new ArrayList<>();
        SQLiteDatabase sqLiteDatabase = this.getReadableDatabase();
        String query = " SELECT * FROM " + NOTES_TABLE;
        Cursor cursor = sqLiteDatabase.rawQuery(query, null);

        if (cursor.moveToFirst()) {

            do {
                int id = cursor.getInt(0);
                String content = cursor.getString(1);
                long date = cursor.getLong(2);
                NoteHelper noteHelper = new NoteHelper(id, content, date);
                notesList.add(noteHelper);
            }
            while (cursor.moveToNext());

        } else {
            sqLiteDatabase.close();
            cursor.close();
        }

        return notesList;
    }

    private List<ChecklistHelper> getChecklists(){
        List<ChecklistHelper> checklistsList = new ArrayList<>();
        SQLiteDatabase sqLiteDatabase = this.getReadableDatabase();
        String query = "SELECT * FROM " +  CHECKLIST_TABLE;
        Cursor cursor = sqLiteDatabase.rawQuery(query, null);

        if (cursor.moveToFirst()){

            do{
                int id = cursor.getInt(0);
                String content = cursor.getString(1);
                long date = cursor.getLong(2);
                ChecklistHelper checklistHelper = new ChecklistHelper(id, content, date);
                checklistsList.add(checklistHelper);

            }
            while (cursor.moveToNext());

        }

        else {
            cursor.close();
            sqLiteDatabase.close();
        }
        return checklistsList;
    }

    public void getNotesAndChecklists(OnContentReadyListener listener) {
        if(listener == null) return; // Don't proceed if the listener is null
        dbexecutor.execute(()-> {
            List<NoteHelper> notes = getNotes();
            List<ChecklistHelper> checks = getChecklists();
            // Merge lists and sort items by creation date
            List<BaseItem> items = new ArrayList<>();
            items.addAll(notes);
            items.addAll(checks);
            Collections.sort(items, new ItemLongDateComparator());
            new Handler(Looper.getMainLooper()).post(()-> listener.onContentReady(items));
        });
    }


    @MainThread
    public interface OnContentReadyListener {
        void onContentReady(List<BaseItem> combinedItems);
    }

}

Connecting the Data to the UI

Now that we've done with the data organisation, we can adapt this design easily to the UI API. In the adapter class we simply use a list of BaseItem class since this list will accept all derived classes from the BaseItem. It will be easier to manage only one list instead of two.

AdapterClass.java

public class AdapterClass extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    List<BaseItem> itemsList;


    public AdapterClass() {
        itemsList = new ArrayList<>();
    }


    @Override
    public int getItemViewType(int position) {
        return itemsList.get(position).getViewType();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        if(viewType == NoteHelper.VIEW_TYPE){

            View view = layoutInflater.inflate(R.layout.layout_note, parent,false);
            return new NoteViewHolder(view);
        }

        if(viewType == ChecklistHelper.VIEW_TYPE){
            View view = layoutInflater.inflate(R.layout.layout_checklist, parent, false);
            return new ChecklistViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        int vtype = getItemViewType(position);
        BaseItem bi = itemsList.get(position);
        if(vtype == NoteHelper.VIEW_TYPE) {
            NoteViewHolder nvh = (NoteViewHolder) holder;
            TextView tv = nvh.itemView.findViewById(R.id.tv_note);
            tv.setText(bi.getContent());
        }
        else if(vtype == ChecklistHelper.VIEW_TYPE) {
            ChecklistViewHolder cvh = (ChecklistViewHolder) holder;
            TextView tv = cvh.itemView.findViewById(R.id.tv_checklist);
            tv.setText(bi.getContent());

        }
    }

    @Override
    public int getItemCount() {
        return itemsList.size();
    }

    public void update(List<BaseItem> combinedList) {
        itemsList.clear();
        itemsList.addAll(combinedList);
        notifyItemRangeChanged(0, itemsList.size());
    }

    static class NoteViewHolder extends RecyclerView.ViewHolder{
        public NoteViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    static class ChecklistViewHolder extends RecyclerView.ViewHolder{
        public ChecklistViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}

Reflecting the Data to the UI

Finally we can reflect the data to the UI parts. Remember when I mentioned about finishing activities instead of restarting the main activity again and again? Look at the adding activities how simple they are now.

activity_add_note.java

public class activity_add_note extends AppCompatActivity {
    Button btnSaveNote;
    EditText etNote;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_note);

        btnSaveNote = findViewById(R.id.btn_save_note);
        etNote = findViewById(R.id.et_note);

        btnSaveNote.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String note = etNote.getText().toString();
                DbHelper  dbHelper = new DbHelper(getApplicationContext());
                dbHelper.insertNote(note, System.currentTimeMillis());

                // Finish this activity
                new Handler(Looper.getMainLooper()).postDelayed(() -> finish(), 500);

            }
        });
    }
}

activity_add_checklist.java

public class activity_add_checklist extends AppCompatActivity {
    Button btnSaveChecklist;
    EditText etChecklist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_checklist);

        btnSaveChecklist = findViewById(R.id.btn_save_checklist);
        etChecklist = findViewById(R.id.et_checklist);
        btnSaveChecklist.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String checklist = etChecklist.getText().toString();
                DbHelper dbHelper = new DbHelper(getApplicationContext());
                dbHelper.insertChecklist(checklist, System.currentTimeMillis());

                // Finish this activity
                new Handler(Looper.getMainLooper()).postDelayed(() -> finish(), 500);
            }
        });
    }
}

One important thing to note in the main activity is that we query the items every time it resumes. Thus when we return from other activities the newly added items will appear in the list.

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    RecyclerView recyclerView;
    AdapterClass adapterClass;
    Button btnNote, btnChecklist;
    DbHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = findViewById(R.id.recycler_view);
        btnNote = findViewById(R.id.btn_add_note);
        btnChecklist = findViewById(R.id.btn_add_checklist);
        recyclerView = findViewById(R.id.recycler_view);
        dbHelper = new DbHelper(this);

        adapterClass = new AdapterClass();
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(adapterClass);

        btnNote.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this,activity_add_note.class);
                startActivity(intent);

            }
        });

        btnChecklist.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, activity_add_checklist.class);
                startActivity(intent);

            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
        if(dbHelper == null) {
            dbHelper = new DbHelper(this);
        }
        dbHelper.getNotesAndChecklists(new DbHelper.OnContentReadyListener() {
            @Override
            public void onContentReady(List<BaseItem> combinedItems) {
                adapterClass.update(combinedItems);
            }
        });
    }
}

Get the whole project: your app

Related