To clarify what I mean.
Model(s)
You need to flatten your model so it can be a list of "Things".
You can have an abstract Thing that contains some basic model info, like an identifier and maybe a parentIdentifier if you're concerned about how to link the things together.
Ultimately, each model needs to have at least a type. E.g.: (this is going to be kotlin pseudo-code, just letting you know..., there may be typos or inconsistencies, but this is to give you an idea how to solve your data problem)
RemoteThing (and RemoteYear, month, etc) are imaginary classes that I think you may have if you had parsed your JSON into "some form of data model".
Now this process is going to transform this RemoteDomainObjects into Local Domain problems (that is, data suited to your local domain/problem).
// type can be an "enum" I just simplified with an int. 1 = year, 2 = month, 3 = day, 4 = stuff. :)
// The "Base" of your model(s)
class Thing(val id: String, val type: Int, val parentId: String? = null)
// Years don't have a "parent" (they are "root elements")
class Year(val id: String, val year Int) : Thing(id, type = 1, parentId = null)
// Month's parentId is gonna be the Year's ID this month is in.
class Month(val id: String, val month: Int, val parentId: String?) : Thing(id, type = 2, parentId = ...)
// Repeat for day...
class Day(val id: String, val day Int, val parentId: String?, events: List<Events>) : Thing(id, type = 3, parentId = ...)
// E.g of an "event" (if you need id/parent, add those too)
data class Events(val title: String, val desc: String)
Flattening the Data
You receive your JSON array of array of array of arrays... somewhere in your code.
Call a UseCase or create a class MyDataTransformator :) that will receive all this Json (or the List<List<List...> if you already created that model) and iterates it one by one, creating a flat List<Thing>().
Pseudo code time...
Imagine you have a List<RemoteThings...> (this is what you parsed your JSON into)
- Iterate the root (years), outer loop.
val results = mutableListOf<Thing>()
// You're gonna need a list of years
val years = mutableListOf<Year>()
remoteThings.forEach { rt -> //remote thing :)
// you're iterating the years... so create a new Year...
val year = Year(rt.id, year = rt.year) //years don't have parentId.
// Each year has it's own list of months.
val months = mutableListOf<Month>()
// Now you need to iterate the months inside this year...
rt.months.forEach { rm -> //remoteMonth ;)
val month = Month(rm.id, rm.month, parentId = year.id)
val days = mutableListOf<Day>()
// Now you need to iterate the days inside this month...
rm.days.forEach { rd -> //remoteDay
val day = Day(rd.id, rd.day, parentId = month.id)
val events = mutableListOf<Events>() //each day gets its own events' list
// Iterate the "stuff" (events... for this day)
rd.events.forEach { re -> //remoteEvent
val event = Event(re.title, re.desc) //does this need a parent? maybe?
// add each event to this list..
events.add(event)
}
// finished iterating the "Day" events, save them
day.events = events
// Save the day (always wanted to say that)
days.add(day)
}
// Finished with the days on this month, save them in the month.
month.days = days
// save the month
months.add(month)
}
// Finished with the months of this year, save them in the year...
year.months = months
// and finally, save the year
years.add(year)
}
// finished iterating all the years... save the "things" and pass this onto your adapter.
results.addAll(years)
I hope that makes sense.
Finally have an class yourAdapter : ListAdapter<Thing, ViewModel>(...) and do adapter.submitList(results) and your 2nd problem begins.
Adapter
I won't go into too much detail here (please check the infinite number of examples of RecyclerViews with Types and nested Types like the one I linked above that shows you how you can differentiate between a flat object to inflate a different viewHolder that will display a different UI.
In that example, you can see there are two types of objects:
ColoredViewHolder and AdViewHolder.
They inflate different layouts and therefore look different.
DiffUtil?
Notice you need a DiffUtil.ItemCallback<...> to pass onto your ListAdapter's constructor. In that same file you can see a simple example of one (basically, it allows the adapter to compare items to know which/when/what changed when you submitList(..).
Conclusion
You started the journey hitting the wall against how to handle nested and structured data in a RecyclerView, and now I've shifted your problem towards a data Transformation and then a presentation issue, but both have a better synergism with each other, since each problem makes the life of the other problem easier.
Horizontal?
As for the horizontal display of day events, you can have your EventListViewHolder have and manage a small(er) internal RecyclerView in horizontal mode (and its adapter) or you can try to leverage something like a HorizontalScrollView; in the end it doesn't matter, so as long as you keep the problems contained and separated. That is: the external RV, doesn't care/know how this is displayed by the ViewHolder; and neither do the Data Transformation process above.
Each class/function has a small/limited amount of responsibilities.
Nesting Loops?!!
Yes, I know it looks crazy, but such is the data you have, nested and in collections, so you ought to transverse it to transform it. There are many ways to do that, mine was pseudo code to give you an idea what you have and what you want.
If you use Coroutines, that could be a suspend function that does this in the computing "dispatcher" while your ViewModel waits for the data before it can pass it onto the UI.
This is the way (I'd go with) :)
Good luck!