How to add date separators in recycler view using Paging Library? - android

After a lot of searching, I know its possible with regular adapter, but I have no idea how to do it using Paging Library. I don`t need code just a clue.
Example

To add separators, you essentially have 2 options:
View-based, you explicitly include separators as an 'item' in the list and define a new viewtype for those separators. Allows the list to re-use the separator views but means you need to take the separators into account when defining the data.
Data-based, each item actually has a separator view, but it only shows on specific items. Based on some criteria you show or hide it whilst binding the view-holder.
For the paging library only option 2 is viable since it only partially loads the data and inserting the separators becomes much more complicated. You will simply need to figure out a way to check if item x is a different day than item x-1 and show/hide the date section in the view depending on the result.

You can achieve the same result using insertSeparators in Paging 3 library.
Make sure your items are sorted by date.
Inside or viewmodel retrieve a Pager something like that
private val communicationResult: Flow<PagingData<CommunicationHistoryItem>> = Pager(
PagingConfig(
pageSize = 50,
enablePlaceholders = false,
maxSize = 400,
initialLoadSize = 50
)
) {
CommunicationPagingSource(repository)
}.flow.cachedIn(viewModelScope)
After all insert separators like a header
val groupedCommunicationResult = communicationResult
.map { pagingData -> pagingData.map { CommunicationHistoryModel.Body(it) } }
.map {
it.insertSeparators{ after, before ->
if (before == null) {
//the end of the list
return#insertSeparators null
}
val afterDateStr = after?.createdDate
val beforeDateStr = before.createdDate
if (afterDateStr == null || beforeDateStr == null)
return#insertSeparators null
val afterDate = DateUtil.parseAsCalendar(afterDateStr)?.cleanTime()?.time ?: 0
val beforeDate = DateUtil.parseAsCalendar(beforeDateStr)?.cleanTime()?.time ?: 0
if (afterDate > beforeDate) {
CommunicationHistoryModel.Header( DateUtil.format(Date(beforeDate))) // dd.MM.yyyy
} else {
// no separator
null
}
}
}
cleanTime is required for grouping by dd.MM.yyyy ignoring time
fun Calendar.cleanTime(): Date {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
return this.time
}

I was in the same spot as you and I came up with this solution.
One important note though, in order to implement this I had to change my date converter to the database, from long to string to store a timestamp
these are my converters
class DateConverter {
companion object {
#JvmStatic
val formatter = SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH)
#TypeConverter
#JvmStatic
fun toDate(text: String): Date = formatter.parse(text)
#TypeConverter
#JvmStatic
fun toText(date: Date): String = formatter.format(date)
}
}
Some starting info though, I have a list of report headers that I wish to show , and page through and be able to filter
They are represented by this object:
data class ReportHeaderEntity(
#ColumnInfo(name = "id") override val id: UUID
, #ColumnInfo(name = "name") override val name: String
, #ColumnInfo(name = "description") override val description: String
, #ColumnInfo(name = "created") override val date: Date)
I also wanted to add separators between the items in the list to show them by date
I achieved this by doing the following:
I created a new query in room like this
#Query(
"SELECT id, name, description,created " +
"FROM (SELECT id, name, description, created, created AS sort " +
" FROM reports " +
" WHERE :filter = '' " +
" OR name LIKE '%' || :filter || '%' " +
" OR description LIKE '%' || :filter || '%' " +
" UNION " +
" SELECT '00000000-0000-0000-0000-000000000000' as id, Substr(created, 0, 9) as name, '' as description, Substr(created, 0, 9) || '000000' AS created, Substr(created, 0, 9) || '256060' AS sort " +
" FROM reports " +
" WHERE :filter = '' " +
" OR name LIKE '%' || :filter || '%' " +
" OR description LIKE '%' || :filter || '%' " +
" GROUP BY Substr(created, 0, 9)) " +
"ORDER BY sort DESC ")
fun loadReportHeaders(filter: String = ""): DataSource.Factory<Int, ReportHeaderEntity>
This basically creates a separator line for all the items I have filtered through
it also creates a dummy date for sorting (with the time of 25:60:60 so that it will always appear in front of the other reports)
I then combine this with my list using union and sort them by the dummy date
The reason I had to change from long to string is because it is much easier to create dummy dates with string in sql and seperate the date part from the whole date time
The above creates a list like this:
00000000-0000-0000-0000-000000000000 20190522 20190522000000
e3b8fbe5-b8ce-4353-b85d-8a1160f51bac name 16769 description 93396 20190522141926
6779fbea-f840-4859-a9a1-b34b7e6520be name 86082 description 21138 20190522141925
00000000-0000-0000-0000-000000000000 20190521 20190521000000
6efa201f-d618-4819-bae1-5a0e907ddcfb name 9702 description 84139 20190521103247
In my PagedListAdapter I changed it to be an implementation of PagedListAdapter<ReportHeader, RecyclerView.ViewHolder> (not a specific viewholder)
Added to the companion object:
companion object {
private val EMPTY_ID = UUID(0L,0L)
private const val LABEL = 0
private const val HEADER = 1
}
and overrode get view type like so:
override fun getItemViewType(position: Int): Int = if (getItem(position)?.id ?: EMPTY_ID == EMPTY_ID) LABEL else HEADER
I then created two seperate view holders :
class ReportHeaderViewHolder(val binding: ListItemReportBinding) : RecyclerView.ViewHolder(binding.root)
class ReportLabelViewHolder(val binding: ListItemReportLabelBinding) : RecyclerView.ViewHolder(binding.root)
and implemented the other overriden methods like so:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
HEADER -> ReportHeaderViewHolder(DataBindingUtil.inflate(inflater, R.layout.list_item_report, parent, false))
else -> ReportLabelViewHolder(DataBindingUtil.inflate(inflater, R.layout.list_item_report_label, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val reportItem = getItem(position)
when (getItemViewType(position)) {
HEADER -> {
(holder as ReportHeaderViewHolder).binding.apply {
report = reportItem
executePendingBindings()
}
}
LABEL -> {
(holder as ReportLabelViewHolder).binding.apply {
date = reportItem?.name
executePendingBindings()
}
}
}
}
I hope this helps and inspires people to find even better solutions

When binding the data pass in the previous item as well
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val previousItem = if (position == 0) null else getItem(position - 1)
holder.bind(item, previousItem)
}
Every view then sets a header, which is only made visible if the previous item doesn't have the same header.
val previousHeader = previousItem?.name?.capitalize().first()
val header = item?.name?.capitalize()?.first()
view.cachedContactHeader.text = header
view.cachedContactHeader.isVisible = previousHeader != header

Kiskae's answer is excellent and for your case option 2 probably works well.
In my case I wanted to have one additional item that wasn't in the database, like this:
Show all
Item 1
Item 2
It needed to be clickable as well. There's the usual way of overriding getItemCount to return +1 and offsetting positions for the other methods.
But I stumbled on another way that I haven't seen documented yet that might be useful for some cases. You might be able to incorporate additional elements into your query using union:
#Query("select '' as name, 0 as id " +
"union " +
"select name, id from user " +
"order by 1 asc")
DataSource.Factory<Integer, User> getAllDataSource();
That means the data source actually returns another item in the beginning, and there's no need to adjust positions. In your adapter, you can check for that item and handle it differently.
In your case the query would have to be different but I think it would be possible.

Related

Kotlin ActivityMainBinding build id in for loop

I have a problem. In my xml file, I have multiple textViews with the following id's:
txtRow1Column1
txtRow1Column2
txtRow2Column1
txtRow2Column2
txtRow3Column1
txtRow3Column2
txtRow4Column1
txtRow4Column2
Now I filled an array with random numbers between 1..2 and using the 1 or 2 I want to add a text in the textview from the resources. For that I already have the following function:
private fun startNewRound() {
// Define the array for (row, column, value)
val rows = arrayOf<Array<Int>>();
// Fill the array for each row and for each column
for (row in 1..4) {
rows.set(row, emptyArray())
for (column in 1..2) {
rows[row].plus((1..2).random())
}
}
// Show the correct text using the filled array
for (columns in rows) {
for (value in columns) {
val id = "txtRow" + rows.indexOf(columns) + "Column" + (columns.indexOf(value) + 1)
when (value) {
1 -> binding."ID HERE".text = getString(R.string.strTrue)
2 -> binding."ID HERE".text = getString(R.string.strFalse)
}
}
}
}
Currently I have added "ID HERE" instead of the id of the view, because I need to use a string as id to bind to the view. My question is: How can I use that string to find the correct id and be able to bind to that specific view?
Side note, the way you're building that 2D array looks like O(n^2), even if it wasn't going to throw IndexOutOfBoundsExceptions.
The way you're getting the column index won't work. Suppose you have the value 1 twice in a column. indexOf is going to return 0 both times because it returns the first valid match. Also, using indexOf inside your 2D loop results in O(n^3) complexity when it could just be O(n).
As to your problem, you can use Resources.getIdentifier() to find the Int ID of a view so you can find the view using findViewById. The alternative would be to use reflection on the binding class to find a field with that name, but that's uglier.
fun <T> View.getChildWithName(name: String): T {
val id = context.resources.getIdentifier("id", name, context.packageName)
return findViewById(id)
}
private fun startNewRound() {
val rows = Array(4) { Array(2) { (1..2).random() } }
rows.forEachIndexed { rowIndex, row ->
row.forEachIndexed { colIndex, value ->
val id = "txtRow${rowIndex + 1}Column${colIndex + 1}"
val view = binding.root.getChildWithName<TextView>(id)
view.text = when (value) {
1 -> getString(R.string.strTrue)
2 -> getString(R.string.strFalse)
}
}
}
}

LiveData in Nested RecyclerView

The fragment I am coding right now is supposed to give the user a calendaric overview of his meal planning schedule. So via date picker, he can choose a time period and the program will show the user which recipes he has chosen for the chosen weekdays.
So I build a nested RecyclerView with the weekdays as parent layer and corresponding recipes as a child layer. The data class for the weekday layer looks like this :
data class Weekday (
val weekday : String,
val listWithRecipes : List<Recipe>?
)
The class for the Recipe entity looks like this:
#Entity(tableName = "Recipe")
#Parcelize
data class Recipe(
#PrimaryKey var recipeName : String,
var description : String?,
var serving : Int,
var preparationTime : Int?
) : Parcelable
The Adapter for the top Recycler View like this :
class MealPlanAdapter(private var mealplan: List<Weekday>) :
RecyclerView.Adapter<MealPlanAdapter.MealPlanViewHolder>(), RecipeAdapter.OnItemClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MealPlanViewHolder {
return MealPlanViewHolder(
DailyMealplanItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun getItemCount() = mealplan.size
override fun onBindViewHolder(holder: MealPlanViewHolder, position: Int) {
val weekday = mealplan[position]
val recipeAdapter = RecipeAdapter(this)
recipeAdapter.submitList(weekday?.listWithRecipes)
holder.dayOfWeek.text = weekday.weekday
val recipeLayoutManager = LinearLayoutManager(holder.recyclerView.context,RecyclerView.VERTICAL, false)
recipeLayoutManager.initialPrefetchItemCount = 4
holder.recyclerView.apply{
layoutManager = recipeLayoutManager
adapter = recipeAdapter
}
}
fun setSchedule(mealplan : List <Weekday>){
this.mealplan = mealplan
notifyDataSetChanged()
}
inner class MealPlanViewHolder(val binding: DailyMealplanItemBinding) :
RecyclerView.ViewHolder(binding.root) {
val recyclerView: RecyclerView = binding.rvRecyclerView
val dayOfWeek: TextView = binding.tvDayOfWeek
}
override fun onItemClick(recipe: Recipe) {
TODO("Not yet implemented")
}
}
Whenever the user changes the time period, the setScheduled() method in the adapter gets called in the fragment.
materialDatePickerStartDate.addOnPositiveButtonClickListener(
MaterialPickerOnPositiveButtonClickListener<Any?> { selection ->
_binding.viewmodel!!.startDateInUTCFormat = selection as Long
_binding.tvStartDate.setText(materialDatePickerStartDate.headerText)
adapter.setSchedule(
_binding.viewmodel!!.returnListWithWeekDaysAndCorrespondingRecipes(
_binding.viewmodel!!.startDateInUTCFormat,
_binding.viewmodel!!.endDateInUTCFormat
)
)
}
)
The viewmodel looks like this :
#HiltViewModel
class MealplanViewModel #Inject constructor(
val mealPlanRepository: MealPlanRepository
) : ViewModel() {
private lateinit var _binding: FragmentMealPlanBinding
var startDateInUTCFormat: Long = System.currentTimeMillis()
var endDateInUTCFormat: Long = System.currentTimeMillis()
fun returnListWithWeekDaysAndCorrespondingRecipes(
startDate: Long,
endDate: Long
): ArrayList<Weekday> {
var startDate = Date(startDateInUTCFormat)
var endDate = Date(endDateInUTCFormat)
var startDateCalendar = dateToCalendar(startDate)
var endDateCalendar = dateToCalendar(endDate)
val calendarDays = createListWithCalendarDates(startDateCalendar, endDateCalendar)
return createListWithWeekDaysAndCorrespondingRecipes(calendarDays)
}
fun dateToCalendar(date: Date): Calendar {
var calInstance = Calendar.getInstance()
calInstance.setTime(date)
return calInstance
}
fun createListWithCalendarDates(
startDateCalendar: Calendar,
endDateCalendar: Calendar
): ArrayList<Calendar> {
var listWithCalendarDates = arrayListOf<Calendar>()
while (startDateCalendar <= endDateCalendar) {
listWithCalendarDates.add(startDateCalendar.clone() as Calendar)
startDateCalendar.add(Calendar.DATE, 1)
}
return listWithCalendarDates
}
fun createListWithWeekDaysAndCorrespondingRecipes(calendarDays: ArrayList<Calendar>): ArrayList<Weekday> {
var dayOfWeekAsString: String
var listWithDaysOfWeeksAndRecipes = arrayListOf<Weekday>()
var flattenedListWithRecipes: List<Recipe>?
for (i in 0 until calendarDays.size) {
var dayOfWeekAsInt = calendarDays[i].get(Calendar.DAY_OF_WEEK)
dayOfWeekAsString = when (dayOfWeekAsInt) {
1 -> "Sunday"
2 -> "Monday"
3 -> "Tuesday"
4 -> "Wednesday"
5 -> "Thursday"
6 -> "Friday"
else -> "Saturday"
}
var calendarDateInString =
transformCalendarDateIntoRequiredStringFormat(calendarDays[i])
var listWithDateAndCorrespondingRecipes: List<MealplanScheduleWithRecipes> =
listOf()
var liveDatalistWithDateAndCorrespondingRecipes =
mealPlanRepository.getMealplanScheduleWithRecipes(calendarDateInString)
liveDatalistWithDateAndCorrespondingRecipes.observeForever() { list ->
listWithDateAndCorrespondingRecipes = list
var listWithRecipes = listWithDateAndCorrespondingRecipes?.map { it.recipes }
flattenedListWithRecipes = listWithRecipes?.flatten()
var wochentag = dayOfWeekAsString
listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes))
}
}
return listWithDaysOfWeeksAndRecipes
}
fun transformCalendarDateIntoRequiredStringFormat(calendarDate: Calendar): String {
var year = calendarDate.get(Calendar.YEAR)
var month = transformCalendarMonthFormatToCorrectMonth(calendarDate)
var day = calendarDate.get(Calendar.DAY_OF_MONTH)
return "$day" + "$month" + "$year"
}
fun transformCalendarMonthFormatToCorrectMonth(calendarDate: Calendar): String {
var monthCalendarFormat = calendarDate.get(Calendar.MONTH)
var monthCorrectFormat = when (monthCalendarFormat) {
0 -> "1"
1 -> "2"
2 -> "3"
3 -> "4"
4 -> "5"
5 -> "6"
6 -> "7"
7 -> "8"
8 -> "9"
9 -> "10"
10 -> "11"
else -> "12"
}
return monthCorrectFormat
}
fun datesAreReasonable(startDate: Long, endDate: Long): Boolean {
return (startDate <= endDate)
}
}
My problem is the list that is passed to the RecyclerView Adapter consists of Weekday objects, which consist of the name of the weekday and the corresponding recipes (see data class "weekday" on top).
In the method "createListWithWeekDaysAndCorrespondingRecipes" in the viewmodel I create this list in a for loop that gets all weekdays between given Dates and their corresponding recipes. However, the recipes are LiveData fetched asynchronously via Room database query while the names of the weekdays are derived synchronously in the main thread. At the end however when I create the Weekday object
(see listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes) at the end of the for loop) I need them together at the same time. I haven't found a way how I can coordinate this successfully. At the moment the logics for adding the object to the list is in the asynchronous "observeForever" block.
See here:
liveDatalistWithDateAndCorrespondingRecipes.observeForever() { list ->
listWithDateAndCorrespondingRecipes = list
var listWithRecipes = listWithDateAndCorrespondingRecipes?.map { it.recipes }
flattenedListWithRecipes = listWithRecipes?.flatten()
var wochentag = dayOfWeekAsString
listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes))
}
This creates wrong results, probably because the coordination between main thread and the observer thread doesn't work.
If I however take the logics of adding out of the observer block, the list with recipes will give me null, because of the asynchronous character of the query.
I know that I described the problem very badly. Maybe still someone got a grasp of it and can help?
You should try to avoid using observeForever, I expect you are using this inside a fragment or an activity which actually has a lifecyclescope that your observer can use.
Your observer should look something like this
liveDataList.observe(viewLifecycleOwner, { list ->
// The way I do it at the moment I just set the recyclerViews adapter and layoutManager here
// This is not the best way to do it, so please keep that in mind
recyclerView.apply {
adapter = MyAdapter(list)
layoutManager = LinearLayoutManager(requireContext())
}
})
// Or if used inside an activity
liveDataList.observe(this, {})
This way your observer will be attached to your lifecycle and "die" together with your view. Whenever that list changes, you will show all entities in the recyclerView. HOWEVER, when you use an Array together with LiveData the LiveData object never "updates" when you just add something to that value, since the array only is a memory reference to the start of the array.
To counter this whenever you add something to your array you need to refresh the LiveData object in order to trigger an update and all observers.
myLiveDataObject.value.add(someOtherObject)
myLiveDataObject.value = myLiveDataObject.value
myLiveDataObject.value = myLiveDataObject.value triggers all observers that there has been a change, annoying I know
If you use it inside a viewHolder or adapter simply pass the lifecycle along with the list
I am also quite new to kotlin, keep that in mind and I guarantee you there is a better way to do this, but hope it helps

How to update object value in MutableList?

I have a MutableList in my Android project where i'm adding an object called Articolo, then when a new item is added to that list i need to check if one item with same ID exist and if it does i need to update it's quantity.
The issue is that i'm trying to use MutableList.find to find the object with the same ID and when i find it i'm simply add the quantity to existing quantity but instead it remains immutable.
Here is my Articolo.kt
data class Articolo(var barcode: String, var qta: Int) {
constructor() : this ("", 0)
}
And here is my function where i'm adding data to MutableList
private var articoli = mutableListOf<Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
if (barcode.isEmpty()) {
txtBarcode.requestFocus()
return;
}
articoli.find{
it.barcode == barcode
}?.qta?.plus(qta) ?:
articoli.add(Articolo(barcode, qta))
}
So if i add the first object like barcode: 1111, qty: 1 and then another same object instead of having one element array with qty 2 i still have qty 1..
That's because .plus(Int) returns a new value. You're not changing the property.
Instead you should do:
fun addBarcode(barcode: String, qta: Int) {
val existant = articoli.find { it.barcode == barcode }
if (existant != null) existant.qta += qta
else articoli.add(Articolo(barcode, qta))
}
#VaiTon86 has the answer (you're not actually changing the value in the Articolo object) but really, you should probably be using a Map here anyway:
maximum one of each item
lookup by some value (barcode)
that's a map!
There's a few ways you could implement it, here's one:
val articoli = mutableMapOf<String, Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
articoli.getOrPut(barcode) { Articolo(barcode, 0) }
.let { it.qta += qta }
}
So the getOrPut just adds a new zero-quantity Articolo entry if there isn't already one, and then you add qta to what's already there for that entry.

Android - How to load several lists from API?

I have an API (https://www.thecocktaildb.com/api.php) I want to load all lists one by one. There's a request where I can find all categories and the only difference beetween lists in the filter in URL.request function What should i use to achieve such functionality? The sketch of how it should be. Maybe Paging library can be useful? Please help!
I believe that you can use Paging 3 for that.
I'll assume that you will start this by having all the categories in a list.
class DrinkSource(
private val categories: List<String>
) : PagingSource<String, Drink>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Drink> {
val result = requestFromAPI(params.key ?: categories[0])
val index = categories.indexOf(params.key)
val previous = if (index == 0) null else categories[index - 1]
val next = if (index == categories.size - 1) null else categories[index + 1]
return LoadResult.Page(result, prevKey = previous, nextKey = next)
}
private suspend fun requestFromAPI(category: String): List<Drink> {
// replace this with an API call
return listOf(Drink(1, ""))
}
}
From what i saw of your API there were no pagination in the category query, so this solution would work.

How to insert items in list of item without a lot of iterations

I have a dataClass, which contains a unique code of item, code of the parent and two lists - categories and subcategories.
data class MyItem (
var code: String,
var name: String,*
var data: String,
var count: Int,
var parent: String,
var categories: MutableList<MyItem>,
var subcategories: MutableList<MyItem>
)
I've got from server 3 different items list. And structure that I want to get is:
- listOfTopLevelItems
--- listOfMiddleLevelItems
----- listOfBottomLevelItems
where every topLevelItem contains a list of middleLevelItems and every middle level items contains a list of bottom level items. For that i used code below
for (topItem in topLevelItems) {
for (middleItem in middleLevelItems) {
if (topItem.code == middleItem.parent) {
val middleResultItem = middleItem
for (bottomItem in bottomLevelItems) {
if (middleItem.code == bottomItem.parent) {
middleResultItem.subcategories.add(bottomItem)
}
}
topItem.categories.add(middleResultItem)
}
}
result.add(topItem)
}
But the problem is if i will have a lots of items on bottom level, than it will be a lot of iterations. Is there is another way to solve this?
So what you have is a DAG of depth 3. I am going to make some other adjustments other than just solving your iteration problem.
First, I think the structure of your data classes is a bit redundant for describing a graph of objects. You do not need the category and subcategory fields in my opinion. Stripping out the irrelevant fields, this is what mine would look like:
data class MyItem(
var code: String,
var parent: String? = null,
var categories: MutableList<MyItem> = mutableListOf()
){
val subcategories: List<MyItem>
get() = categories.flatMap { it.categories }
}
A root/top item will be any item where the parent is null. And then its categories are its immediate children, and its sub categories are its grandchildren. I have provided a property here which will take care of grandchildren if you really want that accessor, and it means if you add something to a child, the parents grandchildren will be updated automatically :D.
Now for version 1 of creating the object graph. This keeps things in line with your apparent structure of knowing which ones are roots, children and grand children. But this is not needed as you will see in version 2.
fun main() {
val topItems = listOf(MyItem("1"), MyItem("2"))
val middleItems = listOf(MyItem("1_1", "1"), MyItem("1_2", "1"), MyItem("2_1", "2"))
val bottomItems = listOf(MyItem("1_1_1", "1_1"), MyItem("1_2_1", "1_2"), MyItem("2_1_1", "2_1"))
val topByID = topItems.map { it.code to it }.toMap()
val middleByID = middleItems.map { it.code to it }.toMap()
bottomItems.forEach { middleByID[it.parent]?.categories?.add(it) }
middleItems.forEach { topByID[it.parent]?.categories?.add(it) }
println(topItems)
println(topItems[0].subcategories)
}
But really, all you need to know for building an obect graph is parent child relationship, and they can all just be in a big collection. Then you can rebuild your object graph like this:
fun main() {
val topItems = listOf(MyItem("1", "*"), MyItem("2", "*"))
val middleItems = listOf(MyItem("1_1", "1"), MyItem("1_2", "1"), MyItem("2_1", "2"))
val bottomItems = listOf(MyItem("1_1_1", "1_1"), MyItem("1_2_1", "1_2"), MyItem("2_1_1", "2_1"))
val allItems = topItems + middleItems + bottomItems
val allItemsByID = allItems.map { it.code to it }.toMap()
allItems.forEach {
allItemsByID[it.parent]?.categories?.add(it)
}
println(topItems)
println(topItems[0].subcategories)
}
This is my favorite approach :D

Categories

Resources