How can I concat a call that returns an array and multiple calls for each element of this array? - retrofit

Sorry if that title is not clear enough but I didn't know how to sum it up in one sentence.
I have a webservice that returns an ArrayList of objects named Father.
The Father object is structured like this:
class Father {
ArrayList<Child> children;
}
I have another webservice that returns me the detail of the object Child.
How can I concat the first call that returns me the arraylist of Father and the multiple calls for the multiple objects Child ?
So far I can make the calls separately, like this:
Call for ArrayList of Father
myRepository.getFathers().subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()).subscribeWith(new DisposableSingleObserver<List<Father>>() {
})
multiple call for ArrayList of Child
childListObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.flatMap((Function<List<Child>, ObservableSource<Child>>) Observable::fromIterable)
.flatMap((Function<Child, ObservableSource<Child>>) this::getChildDetailObservable)
.subscribeWith(new DisposableObserver<Child>() {
// do whatever action after the result of each Child
}))

Prerequisite
Gradle
implementation("io.reactivex.rxjava2:rxjava:2.2.10")
testImplementation("io.mockk:mockk:1.10.0")
testImplementation("org.assertj:assertj-core:3.11.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.3.1")
Classes / Interfaces
interface Api {
fun getFather(): Single<List<Father>>
fun childDetailInfo(child: Child): Single<ChildDetailInfo>
}
interface Store {
fun store(father: Father): Completable
fun store(child: ChildDetailInfo): Completable
}
class ApiImpl : Api {
override fun getFather(): Single<List<Father>> {
val child = Child("1")
val child1 = Child("2")
return Single.just(listOf(Father(listOf(child, child1)), Father(listOf(child))))
}
override fun childDetailInfo(child: Child): Single<ChildDetailInfo> {
return Single.just(ChildDetailInfo(child.name))
}
}
data class Father(val childes: List<Child>)
data class Child(val name: String)
data class ChildDetailInfo(val name: String)
Solution
val fathersStore = api.getFather()
.flatMapObservable {
Observable.fromIterable(it)
}.flatMapCompletable {
val detailInfos = it.childes.map { child ->
api.childDetailInfo(child)
.flatMapCompletable { detail -> store.store(detail) }
}
store.store(it)
.andThen(Completable.concat(detailInfos))
}
On each emit of a List of fathers, the list is flatten. The next opreator (flatMapCompletable) will take an Father. The completable will get the details of each Child with Api#childDetailInfo. The result is build by calling the API one by one. There is no concurrency happening wegen "concat". When the father is stored sucessfully, the childs will be stored as-well, when retrieved successfully. If one of the API-calls fails (e.g. network) everything fails, because the onError will be propgated to the subscriber.
Test
#Test
fun so62299778() {
val api = ApiImpl()
val store = mockk<Store>()
every { store.store(any<Father>()) } returns Completable.complete()
every { store.store(any<ChildDetailInfo>()) } returns Completable.complete()
val fathersStore = api.getFather()
.flatMapObservable {
Observable.fromIterable(it)
}.flatMapCompletable {
val detailInfos = it.childes.map { child ->
api.childDetailInfo(child)
.flatMapCompletable { detail -> store.store(detail) }
}
store.store(it)
.andThen(Completable.concat(detailInfos))
}
fathersStore.test()
.assertComplete()
verify { store.store(eq(Father(listOf(Child("1"), Child("2"))))) }
verify { store.store(eq(Father(listOf(Child("1"))))) }
verify(atLeast = 2) { store.store(eq(ChildDetailInfo("1"))) }
verify(atLeast = 1) { store.store(eq(ChildDetailInfo("2"))) }
}
Please provide next time some classes/ interfaces. When your question contains all vital information, you will get an answer quicker.

Related

Async Loading of a TreeView

Hey I am very new to tornadofx struggeling with async loading of data for the treeview. I am loading categories from a rest endpoint, which I want to show in there.
It seems like there's no direct data binding to the children.
when using 'bindChildren' I can provide the observable list, but I have to convert them into Node's. which then would make the populate block kind of obsolete.
What's the recommended way of doing this? I cannot find anything about this.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
val sourceProperty = SimpleListProperty<Category<*>>()
fun loadData() {
// load items for treeview into 'newItems'
sourceProperty.value = newItems
}
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty.value
else -> parent.value.subcategories
}
}
}
}
Assuming that on a button click I call viewmodel.loadData(), I would expect the TreeView to update as soon as there's some new data. (If I would've found a way to bind)
I've never had to use bindChildren for TornadoFX before and your use of async isn't very relevant to what I think is your primary problem. So, admittedly, this question kind of confused me at first but I'm guessing you're just wondering why the list isn't appearing in your TreeView? I've made a test example with changes to make it work.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//Just a dummy category
class ChildCategory(override val name: String) : Category<ChildCategory> {
override val id = name
override val subcategories: List<ChildCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
//filled with dummy data
val sourceProperty = SimpleListProperty<Category<*>>(listOf(
ChildCategory("Categorya"),
ChildCategory("Categoryb"),
ChildCategory("Categoryc"),
ChildCategory("Categoryd")
).asObservable())
fun loadData() {
sourceProperty.asyncItems {
//items grabbed somehow
listOf(
ChildCategory("Category1"),
ChildCategory("Category2"),
ChildCategory("Category3"),
ChildCategory("Category4")
).asObservable()
}
}
}
class TestView : View() {
val model: CategoryViewModel by inject()
override val root = vbox(10) {
button("Refresh Items").action {
model.loadData()
}
add(createTreeView(model.sourceProperty))
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty
else -> parent.value.subcategories
}
}
}
}
}
There are 2 important distinctions that are important.
1. The more relevant distinction is that inside the populate block, root -> listProperty is used instead of root.listProperty.value. This will make your list appear. The reason is that a SimpleListProperty is not a list, it holds a list. So, yes, passing in a plain list is perfectly valid (like how you passed in the value of the list property). But now that means the tree view isn't listening to your property, just the list you passed in. With that in mind, I would be considerate over the categories' subcategory lists are implemented as well.
2. Secondly, notice the use of asyncItems in the ViewModel. This will perform whatever task asynchronously, then set the items to list on success. You can even add fail or cancel blocks to it. I'd recommend using this, as long/intensive operations aren't supposed to be performed on the UI thread.

TornadoFX:proper way to bind model

I was taking a look at this :
tornadofx
and tried to expand on it with database connection and little more options, (not all of them make sense, but its just playing in a sandbox).
Even though table can be directly edited and the data will persist in database, i did try to do edit through text fields too. actual table editing would happen through different view and not table itself, as i said its just example.
Database used is Jetbrains Exposed.
object Categories : IntIdTable() {
val name = varchar("name", 64).uniqueIndex()
val description = varchar("description", 128)
}
class Category(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Category>(Categories)
var name by Categories.name
var description by Categories.description
override fun toString(): String {
return "Category(name=\"$name\", description=\"$description\")"
}
}
now controller looks something like this, functions are just rudimentary and picked as an example.
typealias ModelToDirtyState = Map.Entry<CategoryModel, TableColumnDirtyState<CategoryModel>>
class CategoryModel() : ItemViewModel<Category>() {
val name: SimpleStringProperty = bind(Category::name)
val description: SimpleStringProperty = bind(Category::description)
}
class DBController : Controller() {
val categories: ObservableList<CategoryModel> by lazy {
transaction {
SchemaUtils.create(Categories)
Category.all().map {
CategoryModel().apply {
item = it
}
}.observable()
}
}
init {
Database.connect(
"jdbc:mysql://localhost:3306/test", driver = "com.mysql.cj.jdbc.Driver",
user = "test", password = "test"
)
TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
}
fun deleteCategory(model: CategoryModel) {
runAsync {
transaction {
model.item.delete()
}
}
categories.remove(model)
}
fun updateCategory(model: CategoryModel) {
transaction {
Categories.update {
model.commit()
}
}
}
fun commitDirty(modelDirtyMappings: Sequence<ModelToDirtyState>) {
transaction {
modelDirtyMappings.filter { it.value.isDirty }.forEach {
it.key.commit()
println(it.key)// commit value to database
it.value.commit() // clear dirty state
}
}
}
Just to quickly comment on controller, delete method works as "intended" however the update one does not, it does not work in sense that after using delete item is remove both from database and tableview(underlying list) itself, and when i do update its not, now i know the reason, i call remove manually on both database and list, now for update perhaps i could do change listener, or maybe tornadofx can do this for me, i just cant set it up to do it. Following code will make things clearer i think.
class CategoryEditor : View("Categories") {
val categoryModel: CategoryModel by inject()
val dbController: DBController by inject()
var categoryTable: TableViewEditModel<CategoryModel> by singleAssign()
var categories: ObservableList<CategoryModel> by singleAssign()
override val root = borderpane {
categories = dbController.categories
center = vbox {
buttonbar {
button("Commit") {
action {
dbController.commitDirty(categoryTable.items.asSequence())
}
}
button("Roll;back") {
action {
categoryTable.rollback()
}
}
// This model only works when i use categorytable.tableview.selected item, if i use categoryModel, list gets updated but not the view itself
// Question #1 how to use just categoryModel variable without need to use categorytable.tableview.selecteditem
button("Delete ") {
action {
val model = categoryTable.tableView.selectedItem
when (model) {
null -> return#action
else -> dbController.deleteCategory(model)
}
}
}
//And here no matter what i did i could not make the view update
button("Update") {
action {
when (categoryModel) {
null -> return#action
else -> dbController.updateCategory(categoryModel)
}
categoryTable.tableView.refresh()
}
}
}
tableview<CategoryModel> {
categoryTable = editModel
items = categories
enableCellEditing()
enableDirtyTracking()
onUserSelect() {
//open a dialog
}
//DOES WORK
categoryModel.rebindOnChange(this) { selectedItem ->
item = selectedItem?.item ?: CategoryModel().item
}
// Question #2. why bindSelected does not work, and i have to do it like above
//DOES NOT WORK
// bindSelected(categoryModel)
//
column("Name", CategoryModel::name).makeEditable()
column("Description", CategoryModel::description).makeEditable()
}
}
right = form {
fieldset {
field("Name") {
textfield(categoryModel.name)
}
}
fieldset {
field("Description") {
textfield(categoryModel.description)
}
}
button("ADD CATEGORY") {
action {
dbController.addCategory(categoryModel.name.value, categoryModel.description.value)
}
}
}
}
}
I apologize for huge amount of code, also in last code snipped i left questions in form of comments where i fail to achive desired results.
I am sure i am not properly binding code, i just dont see why, also i sometimes use one variable to update data, my declared one "categoryModel" and sometimes i use tableview.selecteditem, it just seems hacky and i cant seem to grasp way.
Thank you!

javafx binding from list property to arbitrary object property

I am trying to get a class to have a property bound to another class's list property, where the 1st property is derived from a summarizing calculation over the objects in the list. The code below is a simplified version of my production code. (The production code is doing a summary over DateTime objects -- the essential part of the code below is the binding between a list and an object property (here, it is a String for simplicity).)
I have tried various things. One approach was using addListener on the list in the Summary class below but I was running into weird bugs with the listener callback making updates on the Summary object. After doing a bunch of reading I think that a binding between the summary string and the list is more appropriate but I don't know exactly how to hook up the binding to the property?
package com.example.demo.view
import javafx.beans.Observable
import javafx.beans.binding.StringBinding
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleListProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import tornadofx.View
import tornadofx.button
import tornadofx.label
import tornadofx.vbox
class Thing(x: Int) {
val xProperty = SimpleIntegerProperty(x)
val yProperty = SimpleStringProperty("xyz")
}
class Collection {
private var things = FXCollections.observableList(mutableListOf<Thing>()) {
arrayOf<Observable>(it.xProperty)
}
val thingsProperty = SimpleListProperty<Thing>(things)
fun addThing(thing: Thing) {
things.add(thing)
}
}
class Summary(var collection: Collection) {
val summaryBinding = object : StringBinding() {
// The real code is more practical but
// this is just a minimal example.
override fun computeValue(): String {
val sum = collection.thingsProperty.value
.map { it.xProperty.value }
.fold(0, { total, next -> total + next })
return "There are $sum things."
}
}
// How to make this property update when collection changes?
val summaryProperty = SimpleStringProperty("There are ? things.")
}
class MainView : View() {
val summary = Summary(Collection())
override val root = vbox {
label(summary.summaryProperty)
button("Add Thing") {
summary.collection.addThing(Thing(5))
}
}
}
Keep in mind that I made this answer based on your minimal example:
class Thing(x: Int) {
val xProperty = SimpleIntegerProperty(x)
var x by xProperty
val yProperty = SimpleStringProperty("xyz")
var y by yProperty
}
class MainView : View() {
val things = FXCollections.observableList(mutableListOf<Thing>()) {
arrayOf<Observable>(it.xProperty)
}
val thingsProperty = SimpleListProperty<Thing>(things)
val totalBinding = integerBinding(listProperty) {
value.map { it.x }.fold(0, { total, next -> total + next })
}
val phraseBinding = stringBinding(totalBinding) { "There are $value things." }
override val root = vbox {
label(phraseBinding)
button("Add Thing") {
action {
list.add(Thing(5))
}
}
}
}
I removed your other classes because I didn't see a reason for them based on the example. If the collection class has more functionality than holding a list property in your real project, then add just add it back in. If not, then there's no reason to give a list its own class. The summary class is really just two bindings (or one if you have no need to separate the total from the phrase). I don't see the need to give them their own class either unless you plan on using them in multiple views.
I think your biggest problem is that you didn't wrap your button's action in action {}. So your code just added a Thing(5) on init and had no action set.
P.S. The var x by xProperty stuff will only work if you import tornadofx.* for that file.

Kotlin, how to retrieve field value via reflection

So I have hundreds of fields in a couple of classes and I'd like to write some methods on them where they automatically println each field and its corresponding value
At the moment I have this:
inner class Version(val profile: Profile) {
#JvmField val MINOR_VERSION = glGetInteger(GL_MINOR_VERSION)
fun write(file: File? = null) {
//file.printWriter().use { out -> out.pri }
this::class.java.fields.forEach {
println(it.isAccessible)
println(it.getInt(it)) }
}
}
But this is what I get:
false
Exception in thread "main" java.lang.IllegalArgumentException: Can not set final int field uno.caps.Caps$Version.MINOR_VERSION to java.lang.reflect.Field
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeQualifiedIntegerFieldAccessorImpl.getInt(UnsafeQualifiedIntegerFieldAccessorImpl.java:58)
Any idea?
Instead of using Java fields and Java reflection code, you can also use Kotlin properties and Kotlin reflection classes:
class Reflector {
val Foo = 1;
fun printFields() {
this::class.memberProperties.forEach {
if (it.visibility == KVisibility.PUBLIC) {
println(it.name)
println(it.getter.call(this))
}
}
}
}
It seems that you are passing the Field variable it as a parameter getInt whereas the parameter should be the object the field belongs to this:
From the Javadoc for Field.getInt(Object obj):
obj - the object to extract the int value from
Perhaps this is what you meant to do:
class Reflector {
#JvmField val Foo = 1;
fun printFields() {
this.javaClass.fields.forEach {
println(it.isAccessible)
println(it.getInt(this))
}
}
}
fun main(args : Array<String>) {
Reflector().printFields()
}

Tornadofx Javafx - How to reload a view / component

So its a basic question.
What I am trying to achieve is refreshing views from another views.
Lets say I have a view EmployeeTableView which shows a tabular representation of employees by doing a REST API call.
In another view, I have a the filter EmployeeFilterView wherein I have gender, salary range, employee type, etc.
I also have a userContext object in which I store the user preferences. So by default lets say I have stored the value of gender filter to be Male, salary range to be ALL, etc. This object is send as a parameter to the EmployeeTableView.
When the EmployeeTableView is loaded I do a restAPI call with the userContext values to get the employee details. So that works fine. Now I change the gender filter to Female and assign this value in my userContext.
Now if I could just reload the EmployeeTableView with the userContext object, the restapi call would get the updated values.
But how can I do that ?
Also suggest a better approach if you have.
The EventBus is one valid solution to this. Another would be to use a ViewModel or Controller as the UserContext object and let that include the actual observable list of employees and then bind that list to the TableView in EmployeeTableView. Whenever the list in the context is updated, the TableView will update as well.
The filter view would call a function in the UserContext to perform the actual REST call and update the list of employees based on that.
You could create a separate EmployeeQuery object that can be injected into both the EmployeeFilterView and the UserContext so it can extract the selected filter values to perform the query. This query object contains a list of all the search parameters you want to pass to the server.
You could also consider creating a separate scope to keep these components separated if that makes sense to your architecture.
Exactly how you define these components are mostly a matter of taste, here is one suggestion. I used the RangeSlider from ControlsFX for the mock search UI.
To make it easier to imagine how this ties together, here is a screenshot:
(All names and salaries are fiction :)
/**
* The employee domain model, implementing JsonModel so it can be fetched
* via the REST API
*/
class Employee : JsonModel {
val nameProperty = SimpleStringProperty()
var name by nameProperty
val salaryProperty = SimpleIntegerProperty()
var salary by salaryProperty
val genderProperty = SimpleObjectProperty<Gender>()
var gender by genderProperty
override fun updateModel(json: JsonObject) {
with (json) {
name = getString("name")
salary = getInt("salary")
gender = Gender.valueOf(getString("gender"))
}
}
}
enum class Gender { Male, Female }
/**
* Container for the list of employees as well as a search function called by the filter
* view whenever it should update the employee list.
*/
class EmployeeContext : Controller() {
val api: Rest by inject()
val query: EmployeeQuery by inject()
val employees = SimpleListProperty<Employee>()
fun search() {
runAsync {
FXCollections.observableArrayList(Employee().apply {
name = "Edvin Syse"
gender = Gender.Male
salary = 200_000
})
//api.post("employees/query", query).list().toModel<Employee>()
} ui {
employees.value = it
}
}
}
/**
* Query object used to define the query sent to the server
*/
class EmployeeQuery : ViewModel(), JsonModel {
val genderProperty = SimpleObjectProperty<Gender>(Gender.Female)
var gender by genderProperty
val salaryMinProperty = SimpleIntegerProperty(50_000)
var salaryMin by salaryMinProperty
val salaryMaxProperty = SimpleIntegerProperty(250_000)
var salaryMax by salaryMaxProperty
val salaryDescription = stringBinding(salaryMinProperty, salaryMaxProperty) {
"$$salaryMin - $$salaryMax"
}
override fun toJSON(json: JsonBuilder) {
with(json) {
add("gender", gender.toString())
add("salaryMin", salaryMin)
add("salaryMax", salaryMax)
}
}
}
/**
* The search/filter UI
*/
class EmployeeFilterView : View() {
val query: EmployeeQuery by inject()
val context: EmployeeContext by inject()
override val root = form {
fieldset("Employee Filter") {
field("Gender") {
combobox(query.genderProperty, Gender.values().toList())
}
field("Salary Range") {
vbox {
alignment = Pos.CENTER
add(RangeSlider().apply {
max = 500_000.0
lowValueProperty().bindBidirectional(query.salaryMinProperty)
highValueProperty().bindBidirectional(query.salaryMaxProperty)
})
label(query.salaryDescription)
}
}
button("Search").action {
context.search()
}
}
}
}
/**
* The UI that shows the search results
*/
class EmployeeTableView : View() {
val context: EmployeeContext by inject()
override val root = borderpane {
center {
tableview(context.employees) {
column("Name", Employee::nameProperty)
column("Gender", Employee::genderProperty)
column("Salary", Employee::salaryProperty)
}
}
}
}
/**
* A sample view that ties the filter UI and result UI together
*/
class MainView : View("Employee App") {
override val root = hbox {
add(EmployeeFilterView::class)
add(EmployeeTableView::class)
}
}
I ended up using Tornadofx -> EventBus
Basically, when I change any of the filters, I fire an even which rebuilds the Node with the updated values.
Not sure whether the approach is right, that's why still keeping it open for discussion.

Resources