How to show custom info window on map view - java

I need to show a custom info window when I click on a pin.
The pin is directly in style on the layer.
When I creating a map Im getting the style by url:
mapView?.getMapAsync { map ->
map.setStyle(Style.Builder().fromUrl("mapbox://styles/my-style")) {
onMapReady(map)
}
}
Then I define this layer:
fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
val layer = mapboxMap.style?.getLayer("my-layer")
layer?.setProperties(visibility(Property.VISIBLE))
mapboxMap.addOnMapClickListener(this#InfoWindowSymbolLayerActivity)
}
OnMapClick method:
override fun onMapClick(point: LatLng): Boolean {
return mapboxMap?.projection?.toScreenLocation(point)?.let { handleClickIcon(it) }!!
}
HandleClickIcon method:
fun handleClickIcon(screenPoint: PointF): Boolean {
val features = mapboxMap?.queryRenderedFeatures(screenPoint, MARKER_LAYER_ID)
val inflater = LayoutInflater.from(this)
val bubbleLayout = inflater.inflate(R.layout.pin_info, null) as BubbleLayout
val type = features?.get(0)?.getStringProperty(type)
bubbleLayout.tvDefectType.text = type?.let { formatType(it) }
val username = features?.get(0)?.getStringProperty(username)
bubbleLayout.tvDefectInfo.text = username?.let { formatDefectInfo(it) }
val measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
bubbleLayout.measure(measureSpec, measureSpec)
val measuredWidth = bubbleLayout.measuredWidth.toFloat()
bubbleLayout.arrowPosition = measuredWidth / 2 - 5
val bitmap = SymbolGenerator.generate(bubbleLayout)
type?.let { mapboxMap?.style?.addImage(it, bitmap) }
mapboxMap?.let {
it.getStyle { style ->
setUpInfoWindowLayer(style)
}
}
return true
}
Mapbox example uses custom GeoJson:
https://docs.mapbox.com/android/maps/examples/symbol-layer-info-window/
But I need to display the info window above the
pin like this on click

If the layer is already in your style, then you don't need to set its visibility to visible with layer?.setProperties(visibility(Property.VISIBLE)). It's already going to be visible.
I'd follow https://docs.mapbox.com/android/maps/examples/symbol-layer-info-window/ by:
Starting the data loading and bubble window SymbolLayer setup earlier in your code, rather than in onMapClick: https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/dds/InfoWindowSymbolLayerActivity.java#L88
Only changing the select state in onMapClick: https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/dds/InfoWindowSymbolLayerActivity.java#L186-L206

Related

How to get click event for multiple GeoJsonLayers on Google maps

I am trying to add multiple GeoJsonLayer. When user click on 1 polygon I would like to display data of clicked polygon. Those are some of my polygons:
This is my function in which I get data from API and draw geojson layers on google maps.
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
var geojson = ArrayList<GeojsonResponse>()
val userGerkId: String? = SharedPrefManager.getInstance(applicationContext).user.gerkMID
RetrofitClientLands.instance.getLand(userGerkId).enqueue(object : Callback<GeojsonResponse> {
override fun onResponse(
call: Call<GeojsonResponse>,
response: Response<GeojsonResponse>
) {
if (response.code() == 200) {
val body = response.body()
if (body != null) {
for (i in 0 until body.lands.size) {
val geo = body.lands[i]
val geos = geo.get("geometry")
val properties = geo.get("properties")
//Log.i("Properties", properties.toString())
val geometryJson: JSONObject = JSONObject(geos.toString())
val geoJsonData: JSONObject = geometryJson
val layer = GeoJsonLayer(mMap, geoJsonData)
val style: GeoJsonPolygonStyle = layer.defaultPolygonStyle
style.fillColor = resources.getColor(R.color.darkGray)
style.strokeColor = resources.getColor(R.color.darkerGray)
style.strokeWidth = 2f
layer.addLayerToMap()
layer.setOnFeatureClickListener(
GeoJsonOnFeatureClickListener { feature: Feature ->
Toast.makeText(
applicationContext,
"GeoJSON polygon clicked: $properties",
Toast.LENGTH_SHORT
).show()
})
}
} else {
Log.i("Map-error", response.errorBody().toString())
}
}
}
override fun onFailure(call: Call<GeojsonResponse>, t: Throwable) {
Log.i("Map response", t.message.toString())
Toast.makeText(
applicationContext,
"Prišlo je do napake, na novo zaženite aplikacijo",
Toast.LENGTH_LONG
).show()
}
})
// adding marker
mMap.moveCamera(CameraUpdateFactory.newLatLng(LatLng(45.92757404830929, 15.595209429220395)))
mMap.uiSettings.isZoomControlsEnabled = true
mMap.animateCamera( CameraUpdateFactory.zoomTo( 12.5f ) );
}
I tried to set style.isClickable = false and then add code below, but every time i clicked on layer, it returns the same data (because whole map is clickable ig).
mMap.setOnMapClickListener {
Log.i("Map_clicked", "polygon: $properties")
}
So is there any other way of doing this? This thread has the same problem described.
How to add multiple GeoJsonLayer runtime and get click event in android

How can I change my recyclerView items order?

I would like to sort my items in order to put at the beginning chosen elements, I created a small piece of code allowing it which works rather well, but when I update my array in the adapter, the recyclerview is unchanged. I tried to do it with an update function in my adapter, and with the debugger, I see that the passed array is modified but nothing in the view... I also tried to send my array when creating the adapter but nothing changes. More weird, because I also have a filter by alphabetical order which works well.... Anyone have an idea? Thanks
Here's is my sort code which work, I tested it with the debugger:
for (i in 0 until folders.size) {
val isRead: String? = prefNewDir.getString(folders[i].absolutePath, null)
if (isRead != null) {
println("before : "+folders[i] + "et i :" + i)
val old= folders[i]
folders.removeAt(i)
folders.add(0, old)
println("after : "+folders[i] + "et i :" + i)
}
}
Here my update method in my adapter:
fun updateData(data: MutableList<File>) {
println("update before : "+items[0])
items = data
println("update after : "+items[0])
notifyDataSetChanged()
}
Here's my adapter code:
class DirAdapter(
private val context: Context,
private var items: MutableList<File>,
) : RecyclerView.Adapter<DirAdapter.DirViewHolder>() {
private val prefNewDir: SharedPreferences = context.getSharedPreferences("new_files", Context.MODE_PRIVATE)
class DirViewHolder(view : View) : RecyclerView.ViewHolder(view) {
val Dir_name = view.findViewById<TextView>(R.id.file_name)
val DirIsRead = view.findViewById<ImageView>(R.id.file_new)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType : Int):DirViewHolder{
val view : View = LayoutInflater.from(parent.context) //Mode dossier
.inflate(R.layout.folder_element, parent, false)
return DirViewHolder(view)
}
fun updateData(data: MutableList<File>) {
println("update data : "+items[0])
items = data
println("update data : "+items[0])
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: DirViewHolder, position: Int) {
val currentDir = items[position]
holder.Dir_name.text = currentDir.name
val isRead: String? = prefNewDir.getString(currentDir.absolutePath, null) // On recupere si oui ou non le fichier est lu
if (isRead!=null) {
holder.DirIsRead.visibility = View.VISIBLE
} else
holder.DirIsRead.visibility = View.GONE
holder.itemView.setOnClickListener {
val intent = Intent(context, FilesActivity::class.java)
intent.putExtra("path",currentDir.absolutePath)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
holder.itemView.setOnLongClickListener {
val popupMenu = PopupMenu(context, it)
if (isRead != null) { //Popup seulement sur un element
popupMenu.menu.add("Marquer comme lu")
}
popupMenu.setOnMenuItemClickListener {
if (it.title == "Marquer comme lu") {
putFolderAsRead(currentDir)
holder.DirIsRead.visibility = View.GONE
}
true
}
popupMenu.show()
true
}
}
private fun putFolderAsRead(folder: File) {
val editor: SharedPreferences.Editor = prefNewDir.edit()
editor.remove(folder.absolutePath)
//Dossier courant
val folderList = folder.listFiles()
for (dListItem in folderList) {
if(dListItem.isDirectory) //Si un dossier est present on reboucle dessus
putFolderAsRead(dListItem)
editor.remove(dListItem.absolutePath)
println(dListItem.absolutePath + " Marque comme lu !")
}
editor.apply()
}
override fun getItemCount() = items.size
}
First of all for storing data in shared preferences, I'd prefer you to use my library here. You can refer the docs to see how it works. Also, I'd prefer you to use a data class instead of the File class. That won't give you what you want. Since, you already show the files without sort, you just need some modifications:
Make a data class like this:
data class MyFile(var file: File, var isNew: Boolean = false)
Now, the sorting part will be like this:
var folders = getFolders(Environment.getExternalStorageDirectory())
for (i in 0 until folders.size) {
val isRead: String? = prefNewDir.getString(folders[i].absolutePath, null)
folders.get(i).isNew = if(isRead == null) false else true
}
Now also, you might want to get the folder without sorting and you might be a bit confused, so, here is the code for getting the folders
fun getFolders(location:File) : ArrayList<MyFile>(){
val files = location.listFile()
var foldersList = ArrayList<MyFile>()
for (file in files) {
if(file.isDirectory){ foldersList.add(MyFile(file)) }
}
}
This will do the task for you.

Android: Open and read another app location

I need to develop an app which monitors a list of apps and, if the system is rebooted, return to the last open page of the monitored apps. So, say that android was on the youtube app playing lofi hip hop radio - beats to relax/study to. How could my app send android back to this given video? Also, is there a way for me to know where in the app the user is? For example, can I know that the user was on youtube playing lofi and open it back again for them?
I already know how to open another app as well as detect whichever app is currently open, but I need to know the its uri as well. I can use adb and shell script if it is needed.
I have the following code to launch another app:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val intent = Intent(this, BackAppListenerService::class.java)
startService(intent)
openApp(this, "com.google.android.youtube")
}
/** Open another app.
* #param context current Context, like Activity, App, or Service
* #param packageName the full package name of the app to open
* #return true if likely successful, false if unsuccessful
*/
fun openApp(context: Context, packageName: String?): Boolean {
val manager = context.packageManager
return try {
val i = manager.getLaunchIntentForPackage(packageName!!)
if (i == null) {
println("Activity not found")
return false;
//throw new ActivityNotFoundException();
}
//throw new ActivityNotFoundException();
i.addCategory(Intent.CATEGORY_LAUNCHER)
context.startActivity(i)
true
} catch (e: ActivityNotFoundException) {
println(e)
false
}
}
}
And with this I get the current foreground app
class BackAppListenerService : Service() {
private var isRunning = false
private var lastApp = ""
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate() {
isRunning = true
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable {
while (true) {
try {
Thread.sleep(10)
} catch (e: Exception) {
}
val currentForegroundApp = getForegroundApp()
val currentApp = currentForegroundApp.first
if (currentApp != lastApp) {
// New app on front
lastApp = currentApp
println("Current App $lastApp")
}
}
}).start()
return START_STICKY
}
// Must Have Usage Access Permission
fun getForegroundApp(): Pair<String, UsageStats> {
var currentApp = "NULL"
var currentAppInfo: UsageStats? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val usm = this.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()
val appList =
usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time)
if (appList != null && appList.size > 0) {
val mySortedMap: SortedMap<Long, UsageStats> =
TreeMap<Long, UsageStats>()
for (usageStats in appList) {
mySortedMap.put(usageStats.lastTimeUsed, usageStats)
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
currentAppInfo = mySortedMap[mySortedMap.lastKey()]!!
currentApp = mySortedMap[mySortedMap.lastKey()]!!.packageName
}
}
} else {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = am.runningAppProcesses
currentApp = tasks[0].processName
}
return Pair(currentApp.split(".").last(), currentAppInfo!!)
}
[...]
}
Any help is much appreciated.

How to use interfaces as listeners from and Dialoge in Android?

I don't know how I can use a interface in Android as an event listener. I defined an interface ProfilePictureTakenEvent with the method onProfilePictureTaken. In an dialog, which is opened by a button click on a fragment, the user can take an Image with the camera. I used the method startActivityForResult for it. The result (the image) of this action I get in the fragment (the overriden method onActivityResult).
Now my question: How can I show this picture in my dialog when I get the result in my fragment? I thought that I can use an event listener, but I don't know how and didn't find any Explanation.
Here my code:
UserFragment.tk
class UserFragment() : Fragment() {
private var userFragment: UserFragment? = null
private var profilePictureTakenEvent: ProfilePictureTakenEvent? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_user, container, false)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
this.userFragment = this
val database = UserManagerDatabase(view.context)
val repository = UserRepository(database)
val factory = UserViewModelFactory(repository)
val viewModel = ViewModelProviders.of(this, factory).get(UserViewModel::class.java)
val adapter = UserAdapter(listOf(), viewModel)
rvUsers.layoutManager = LinearLayoutManager(view.context)
rvUsers.adapter = adapter
var emailAddresses: List<String> = emptyList()
viewModel.getAll().observe(viewLifecycleOwner, { userList ->
adapter.users = userList
emailAddresses = userList.map { user -> user.email }
adapter.notifyDataSetChanged()
})
viewModel.getEmailAddresses().observe(viewLifecycleOwner, {emailAddresses = it })
fBtnAddUser.setOnClickListener {
AddUserDialog(view.context,
object: UserCreateEvent {
override fun onAddButtonClicked(user: User) {
viewModel.insert(user)
}
}, emailAddresses, userFragment).show()
}
}
fun setProfilePictureTakenEvent(profilePictureTakenEvent: ProfilePictureTakenEvent) {
this.profilePictureTakenEvent = profilePictureTakenEvent
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == AddUserDialog.REQUEST_IMAGE_CAPTURE) {
this.profilePictureTakenEvent?.onProfilePictureTaken(data)
}
}
}
AddUserDialog.kt:
class AddUserDialog(
context: Context,
private var userCreateEvents: UserCreateEvent,
private var emails: List<String>,
private var parentFragment: UserFragment?
) : AppCompatDialog(context), ProfilePictureTakenEvent {
companion object {
const val REQUEST_IMAGE_CAPTURE = 1
}
private var imageData: Bitmap? = null
override fun onProfilePictureTaken(data: Intent?) {
this.ivProfilePicture.setImageBitmap(data?.extras?.get("data") as Bitmap)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_add_user)
setTitle(context.getString(R.string.title_add_user))
val editTextViews: Map<String, EditText> = mapOf(
"first_name" to dAddUserEdFirstName,
"last_name" to dAddUserEdLastName,
"email" to dAddUserEdEmail,
"password" to dAddUserEdPassword,
"password_confirmation" to dAddUserEdPasswordConfirmation
)
dAddUserBtnSave.setOnClickListener {
val firstName = dAddUserEdFirstName.text.toString()
val lastName = dAddUserEdLastName.text.toString()
val username = "${firstName.toLowerCase(Locale.GERMAN)}-${lastName.toLowerCase(Locale.GERMAN)}"
val email = dAddUserEdEmail.text.toString()
val password = dAddUserEdPassword.text.toString()
val passwordConfirmation = dAddUserEdPasswordConfirmation.text.toString()
var error = false
val emptyFields: StringBuilder = StringBuilder()
// Check if any EditText is empty
editTextViews.forEach {(key, editTextView) ->
val textEmpty: Boolean = editTextView.text.toString().isEmpty()
if (textEmpty) {
error = true
emptyFields.append("${context.getString(AppHelper.stringTranslation(key))}, ")
}
AppHelper.updateBackground(context, editTextView, if (textEmpty) R.drawable.input_field_error else R.drawable.input_field)
}
if (error) {
//AppHelper.showToastLong(context, context.getString(R.string.error_field_not_filled, AppHelper.commaStringToCorrectGrammatical(context, emptyFields.toString())))
return#setOnClickListener
}
// Check if E-Mail address is valid
if (AppHelper.invalidEmail(email)) {
AppHelper.showToastShort(context, R.string.error_invalid_email)
return#setOnClickListener
}
// Check if supplied E-Mail address is unique
if (emails.contains(email)) {
AppHelper.showToastShort(context, R.string.error_email_already_taken)
return#setOnClickListener
}
// Check if password and password confirmation match
if (password != passwordConfirmation) {
AppHelper.showToastShort(context, R.string.error_password_confirmation_not_matching)
return#setOnClickListener
}
//TODO: Password hashing
// Creating the User
val user = User(firstName, lastName, username, email, password)
userCreateEvents.onAddButtonClicked(user)
// Closing the dialog after calling the onClickEvent and data is valid
dismiss()
}
btnTakeProfilePicture.setOnClickListener {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
try {
parentFragment?.startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
} catch (e: ActivityNotFoundException) {
AppHelper.showToastLong(context, "Fehler")
}
}
// Close dialog when cancel Button is clicked
dAddUserBtnCancel.setOnClickListener { cancel() }
}
}
ProfilePictureTakenEvent.kt:
interface ProfilePictureTakenEvent {
fun onProfilePictureTaken(data: Intent?)
}
As far I understand, your dialog is still shown after capture the image,
As are create the dialog from that fragment, one of the easy solution can be: hold the object in a global variable, can call a function to show that capture images.
Like:
val dialog = AddUserDialog(....)
inside
onActivityResult(..)
...
dialog.updateImage(data)
Thus actually you will not need any listeners to show the image at dialog.

Implementing MVI Architecture in Android without Mosby

I am trying to implement MVI Architecture in Android, but don't want to use Mosby Library. I want to learn the basics first.
I am building a sample app where when I press a button, text in the textview changes(initially the text is something else). Here is the code for MainActivity and MainPresenter.
class MainActivity : AppCompatActivity(), MainContract.View {
lateinit var mPresenter: MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPresenter = MainPresenter()
mPresenter.attachPresenter(this)
bind()
}
#SuppressLint("CheckResult")
private fun bind() {
mPresenter.states().subscribe({ state ->
render(state)
}, {
Log.e("error", "Error is: ", it)
it.printStackTrace()
})
mPresenter.addIntents(intents())
}
override fun intents(): Observable<MainIntent> {
return Observable.merge(
initialIntent(),
clickIntent()
)
}
override fun render(state: MainViewState) {
btn_show.isEnabled = state.isEnabledButton
helloWorldTextView.text = state.message
loadingIndicator.visibility = if (state.isLoading) View.VISIBLE else View.GONE
}
private fun initialIntent(): Observable<MainIntent.InitialIntent> = Observable.just(MainIntent.InitialIntent)
private fun clickIntent(): Observable<MainIntent.ClickIntent> {
return btn_show.clicks().map { MainIntent.ClickIntent("Eureka") }
}
}
class MainPresenter : MainContract.Presenter {
private val intentsSubject: PublishSubject<MainIntent> = PublishSubject.create()
override fun states(): Observable<MainViewState> {
return statesObservable
}
private lateinit var view: MainContract.View
override fun attachPresenter(view: MainContract.View) {
this.view = view
}
#SuppressLint("CheckResult")
override fun addIntents(intents: Observable<MainIntent>) {
intents.subscribe(intentsSubject)
}
private val reducer =
BiFunction { previousState: MainViewState, result: MainResult ->
when (result) {
is MainResult.InitialResult.InFlight -> previousState.copy(
isLoading = true,
message = "Initial Result",
isEnabledButton = false
)
is MainResult.InitialResult.Success -> previousState.copy(
isLoading = true,
message = "Initial Success",
isEnabledButton = true
)
is MainResult.InitialResult.Error -> previousState.copy(
isLoading = false,
message = "Error Initially",
isEnabledButton = true
)
is MainResult.ClickedResult.Success -> previousState.copy(
isLoading = false,
message = System.currentTimeMillis().toString(),
isEnabledButton = true
)
is MainResult.ClickedResult.Error -> previousState.copy(
isLoading = false,
message = "Error Clicked",
isEnabledButton = true
)
is MainResult.ClickedResult.InFlight -> previousState.copy(
isLoading = true,
message = "Clicked In Flight",
isEnabledButton = false
)
}
}
private fun actionFromIntent(intent: MainIntent): MainAction {
if (intent is MainIntent.InitialIntent) {
return MainAction.InitialAction
} else if (intent is MainIntent.ClickIntent) {
return MainAction.ClickedAction("Hello")
} else {
return MainAction.InitialAction
}
}
private var actionProcessor: ObservableTransformer<MainAction, MainResult> = ObservableTransformer { actions ->
actions.publish { shared ->
Observable.merge<MainResult>(
shared.ofType(MainAction.InitialAction::class.java).compose(initialActionProcessor),
shared.ofType(MainAction.ClickedAction::class.java).compose(clickActionProcessor)
)
}
}
private val initialActionProcessor =
ObservableTransformer<MainAction.InitialAction, MainResult.InitialResult> { action: Observable<MainAction.InitialAction> ->
action.switchMap {
Observable.just("hello initially")
.map { MainResult.InitialResult.Success(it) }
.cast(MainResult.InitialResult::class.java)
.onErrorReturn { MainResult.InitialResult.Error(it.message!!) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.InitialResult.InFlight }
}
}
private val clickActionProcessor =
ObservableTransformer<MainAction.ClickedAction, MainResult.ClickedResult> { action: Observable<MainAction.ClickedAction> ->
Observable.just("Click").map { message ->
MainResult.ClickedResult.Success(message)
}.cast(MainResult.ClickedResult::class.java)
.onErrorReturn { MainResult.ClickedResult.Error("Error") }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.ClickedResult.InFlight }
}
private val statesObservable: Observable<MainViewState> = compose()
private fun compose(): Observable<MainViewState> {
return intentsSubject
.map {
actionFromIntent(it)
}
.compose(actionProcessor)
.scan(MainViewState.idle(), reducer)
.distinctUntilChanged()
.replay(1)
.autoConnect(0)
}
}
Problem is that only the Inital event is fired and nothing else. The code doesn't respond to clicks, render is called only initially once.
Also, if I remove the startWith{} from the actionProcessors code responds to clicks, but only once. After that, nothing happens.
Does anyone see issue with the code? I have been trying to get my head around this problem for a while now.
My previous reply:
It's not straight answer to your question. But if you implement what's below, you probably won't have the problem you actually asked about and you'll have easier MVI solution.
You probably try to merge https://speakerdeck.com/jakewharton/the-state-of-managing-state-with-rxjava-devoxx-us-2017, http://hannesdorfmann.com/android/mosby3-mvi-1 and https://medium.com/#oldergod/managing-state-with-rxjava-b0798a6c5757 ideas.
Take a look here: https://proandroiddev.com/taming-state-in-android-with-elm-architecture-and-kotlin-part-1-566caae0f706 - it's simpler. Part 1 and 2 should be enough.
I tried the 1st approach and was repulsed by initial complexity. In 2nd approach you don't have Action, Intent, Result, but Msg instead. It's simpler to reason about.
There's also new MVI course - but haven't checked it yet.
Current approach:
I tried mentioned Elm Architecture, but it is not complete. There are at least 2 problems:
Only one request can get through queue at one moment. Some RxJava
should do the trick (groupBy with 2 streams: ui, background
probably).
parallel requests would update the same state, so you should differentiate DataStates inside your UiState. So different state for different part of UI.
Before writing actual fixes we realised, this is not the way to go ATM: announced Composables could do the MVI trick: smooth and precise data transition to specific parts of UI.
Disclaimer: moderator removed my answer which WAS actual answer. Even more, my answer moved to comment is cut down, which makes it look unfinished. That's why this post emerged once again. After you read it dear moderator, you can remove disclaimer, thanks :)

Categories

Resources