Search code examples
androidandroid-cursoradapterandroid-cursorcommonsware-cwac

Multiple Tables in Cursor Adapter (or MergeAdapter


I am building a workout tracker that utilizes two tables. One is the Workout_Container , that can have multiple movements. The next is the Movements table, which contains the actual movement (name, weight, percentage, etc.). Each table as a unique key field (_id), but Movement has a field for the Workout_Container's _id, as this is a relational database. So for one record of the Workout Container, it can have multiple entries in the Movement table.

Schema is:

Main(_id INTEGER PRIMARY KEY AUTOINCREMENT, Workout_Date TEXT)      

Workout_Container (_id INTEGER PRIMARY KEY AUTOINCREMENT, Date_ID INTEGER, AMRAP INTEGER, Rounds INTEGER, Finish_Time TEXT, Rounds_Finished INTEGER, IsWeight_Workout TEXT, Workout_Name TEXT, Comments TEXT).  

Movement_Template (_id INTEGER PRIMARY KEY AUTOINCREMENT, Workout_Container_ID INTEGER, Sets INTEGER, Reps INTEGER, Movement TEXT, Time_of_Movement TEXT, Length INTEGER, Weight INTEGER, Percentage INTEGER, Comments TEXT). 

Main is the parent of Workout_Container who is the parent of Workout Template. I only care about the last two tables as the first (although it seems redundant) is being used elsewhere in displaying a calender and I wanted a smaller table for when I'm querying whether or not a workout exists for a given day.

Visually, this would look similar to a List adapter, but in a hierarchical fashion, as the Workout Container would be the parent of any Movements listed underneath of it. The Workout Container will be visually different than the Movements....looking like this...which means that the getView would need to know what specific .xml format to load.

Workout Container (_id = 45)
===============
*Movement 1  (movement with Container_id = 45)*

*Movement 2  (movement with Container_id = 45)*

*Movement 3  (movement with Container_id = 45)*

Workout Container (_id = 46)
==============
*Movement 1   (movement with Container_id = 46)*

*Movement 2   (movement with Container_id = 46)*

Each row is its own View as given by the Adapter (whether CursorAdapter or MergeAdapter). With the user being able to select those rows and edit them or delete them. Yes, I'll have a function to add rows, but haven't gotten there yet. Right now, I merge the two tables in a Cursor query, grabbing all rows in Workout_Container for that day, and all rows in Movement that correspond to the rows grabbed in the Workout_Container via a JOIN. But this gives me a merge of two tables and in a single CursorAdapter, I can't tell if I should be displaying a Workout_Container .xml, or a Movement .xml, as I have no idea of the past row that was displayed when I display the row in getView. The above example would return 5 rows in the query, with Workout Container _id=45 being replicated 3 times in the Cursor, and Workout_Container _id=46 information being repeated 2 times in the Cursor.

My question is this: How would I go about displaying this?

Option 1: Should I merge the two tables into one cursor view? If so, then how do I know which View to put out as the Workout Container information could be replicated for a number of records and I can't look back at the row before to determine if I have to display a new Workout Container or not? CursorAdapter requires a unique ID number for each row, and its possible that I won't have it as each _id for the two tables are independent.

Option 2: Using Merge Adapter, grab a single row of the Workout Container for that day, and put each row in its own cursor. Then grab a single row of each Movement that corresponds to the given Workout Container _id and move that into its own Cursor. Feed each Cursor into the MergeAdapter as its own CursorAdapter. I would basically have an array of CursorAdapters that would have a single row. Basically going through a nested loop that would grab Cursors as it stepped through. Would this be a big performance hit as I may have a number of CursorAdapters open within MergeAdapter?

Option 3: Make a class that replicates each table, and pour a single row into an array of classes, making an array of classes that then gets sent to the MergeAdapter. This seems like the hardest as I would have to override a lot more items that CursorAdapter already takes care of.

Right now, I grab all pertinant rows in Workout_Container and Movements for that given day all at once within an AsyncTask upon Activity Creation in a Fragment that is hidden its only job is AsyncTask items...and hand it back to the Activity. Would I need to have that AsyncTask hand off each row to the Adapter as it goes, creating each view one at a time, rather than taking the entire Cursor of rows and going through it?

I hope this all makes sense as I've been racking my brain over this and the only thing that seems would work easily is Option 2....I searched and didn't find anything that helped with this specific situation.


Solution

  • How would I go about displaying this?

    I'd go with Options #2, #3, or #4.

    Option #4: Use an ExpandableListView, which seems like it has exactly the UI you want, with the added ability for the user to expand and collapse whether to show the movements for a particular workout.

    Would this be a big performance hit as I may have a number of CursorAdapters open within MergeAdapter?

    Not from a display standpoint. It would get a bit expensive to load the data, as you'd be doing N+1 queries: one for the workouts and one for each set of movements for that workout.

    You could improve upon this by creating a CursorWindowAdapter that overrides getCount(), moveToPosition(), and perhaps other stuff to have it "adapt" a subset of a Cursor (start position and # of rows). Then you'd only need two queries: one for the workouts and one for all movements, with the latter ordered by workout. You'd then scan through the movements Cursor, building up CursorWindowAdapter instances for each workout's set of movements. I have no idea how easy or difficult it would be to create a reliable CursorWindowAdapter. And, if your data set is small, you might be able to get away without it.

    This seems like the hardest as I would have to override a lot more items that CursorAdapter already takes care of.

    The bigger question is: what do you intend to do with this data beyond showing it in this list? If your intended uses overall make more sense to be applied to POJOs rather than Cursor entries, converting your data set into a suitable tree of POJOs may be worthwhile. Don't think of display-the-list problem in isolation.

    Would I need to have that AsyncTask hand off each row to the Adapter as it goes, creating each view one at a time, rather than taking the entire Cursor of rows and going through it?

    It'd be the other way around for the MergeAdapter scenarios: you'd have to wait until all queries are done, then build the MergeAdapter in onPostExecute(). The big limitation of MergeAdapter is that once you attach it to the AdapterView, you cannot add more adapters/views to it.