I have an object that can contains urls
class MyObject
(
var url_image:String? = null,
var url_document:String? = null,
var file_image:File? = null,
var file_document:File? = null
)
Also i have a method to download urls, this method returns Observable<File>
fun download_file(url:String): Observable<File>
{
//Some logic for creating file, downloading data from url and returning this file as Observable
}
I need to create a method where i would pass myObject, and if needed it will download it urls and finally return Observable<MyObject>. Something like this:
fun prepareForShare(obj: MyObject): Observable<MyObject>
{
return Observable.just(obj)
.map(
{
if (obj.url_image != null)
{
download_file(obj.url_image!!)
...
.subscribe(
{
obj.file_image = it
})
}
if (obj.url_document != null)
{
download_file(obj.url_image!!)
...
.subscribe(
{
obj.file_document = it
})
}
}))
}
How should i make this chain of requests in a right way?
You can combine all requests using the zip(...) operator and transform the MyObject in the flatMap{...} callback:
fun prepareForShare(obj: MyObject): Observable<MyObject> {
return Observable.zip(
if (obj.url_image != null) {
download_file(obj.url_image!!).flatMap {
obj.file_image = it
return#flatMap Observable.just(obj)
}
} else Observable.just(obj),
if (obj.url_document != null) {
download_file(obj.url_document!!).flatMap {
obj.file_document = it
return#flatMap Observable.just(obj)
}
} else Observable.just(obj),
BiFunction { o1, o2 ->
obj.file_image = o1.file_image
obj.file_document = o2.file_document
obj
}
)
}
Alternatively, for a more cleaner approach, you can wrap File in your custom holder object. For example:
data class MyFile(var file: File?)
and emit MyFile(null) if url is empty:
fun download_file(url:String?): Observable<MyFile>
{
if (url == null) return Observable.just(MyFile(null))
...
}
fun prepareForShare(obj: MyObject): Observable<MyObject> {
return Observable.zip(
download_file(obj.url_image),
download_file(obj.url_document),
BiFunction { file1, file2 ->
obj.file_image = file1.file
obj.file_document = file2.file
obj
}
)
}
Related
How can I send the result to the ViewModel in this case???
val callback: (OAuthToken?, Throwable?) -> Unit = { token, error ->
if (error != null) {
// TODO: error return to viewModel
} else if (token != null) {
// TODO: success return to viewModel
}
}
fun signInWithABC() {
abcApi.signIn(callback = callback)
}
I think signInWithABC should returns to the ViewModel, not from callback directly...
maybe like this..
fun signInWithABC(): Result<AuthData> {
return abcApi.signIn(callback = callback)
}
But, I don't know how to do it..
Should I fix it like this? It doesn't look clean though.
fun signInWithABC() {
abcApi.signIn(){ token, error ->
if (error != null) {
// TODO: error return to viewModel
} else if (token != null) {
// TODO: success return to viewModel
}
}
}
And I also tried it with this.. but it has return problem. lamda can't return the value for the function. But it can only return for the block..
fun signInWithABC(): Result<String> {
abcApi.signIn(){ token, error ->
if (error != null) {
return Result.failure<Throwable>(error)
} else if (token != null) {
return Result.success(token)
}
}
return Result.failure(throw IllegalAccessException())
}
You may need to do callback to suspend conversion.
Here is a simple example of doing this:
suspend fun signInWithABC(): String = suspendCoroutine { continuation ->
abcApi.signIn(){ token, error ->
if (error != null) {
continuation.resume("Error")
} else {
continuation.resume(token) // Assuming token is a string
}
}
}
I am debugging an application that communicates with an IoT device via http.
In response to commands, the device sends information in xml format.
An application can also receive binary data on a GET request.
In the functionality of the application, filling the RecyclerView from the list and loading images to fill the RecyclerView and executing individual commands to change modes.
The problem is that the device does not have the most powerful processor, and when a large number of http commands are received, the service cannot cope and hangs for a long time until the WiFi channel fails.
I can’t figure out how to organize interaction so that each next command waits for the previous one to complete. The solution is complicated by the fact that populating the RecyclerView, loading images, and executing commands are in different parts of the code, and each is executed asynchronously.
Populating RecyclerView:
private fun initViewModel(filter: String) {
val st = Storage(requireContext())
val cache = "${st.externalCacheDir}/$filter/"
val viewModel = ViewModelProvider(this).get(DeviceListViewModel::class.java)
viewModel.getRecycerListObserver().observe(requireActivity(), Observer<ResponseData> {
if (it != null) {
val media = it.mediaData?.filter { it.mediaData?.fPath!!.contains(filter, false) }
mediaList = arrayListOf()
if (media != null) {
for (i in media.sortedByDescending { it.mediaData?.fTimeCode }) {
i.mediaData?.let { it1 -> mediaList.add(it1) }
}
}
viewModel.recyclerListLiveData = MutableLiveData()
ThumbDownloader(dataAdapter, mediaList, cache, swipeLayout).execute()
} else {
Toast.makeText(activity, "Error in getting data", Toast.LENGTH_SHORT).show()
}
})
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeApiCall()
}
}
ViewModel:
class DeviceListViewModel : ViewModel() {
var recyclerListLiveData: MutableLiveData<ResponseData>
init {
recyclerListLiveData = MutableLiveData()
}
fun getRecycerListObserver(): MutableLiveData<ResponseData> {
return recyclerListLiveData
}
fun makeApiCall() {
viewModelScope.launch(Dispatchers.IO) {
try {
val retroInstance =
RetroInstance.getRetroInstance(MainActivity.BaseUrl).create(RetroService::class.java)
val response = retroInstance.getDataFromApi(1, Cmd.WIFIAPP_CMD_FILELIST)
recyclerListLiveData.postValue(response)
} catch (e: Exception) {
var response: ResponseData? = null
when (e) {
is ConnectException -> {
recyclerListLiveData.postValue(response)
}
is SocketTimeoutException -> {
recyclerListLiveData.postValue(response)
}
}
}
}
}
}
Service to make a command (processing results in the Handler):
class DeviceService {
private val handler: Handler
private var mJob: Job? = null
constructor(handler: Handler) {
this.handler = handler
}
fun sendCommand(cmd: Int) {
val service = RetroInstance.buildService(MainActivity.BaseUrl, RetroService::class.java)
mJob = CoroutineScope(Dispatchers.IO).launch {
val response = when (cmd) {
Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
try {
service.getLinkFromApi(1, cmd)
} catch (e: Exception) {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
mJob?.cancel()
}
}
else -> {
try {
service.makeCommand(1, cmd)
} catch (e: Exception) {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
mJob?.cancel()
}
}
}
withContext(Dispatchers.Main) {
try {
when (cmd) {
Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
handler.obtainMessage(Msg.MESSAGE_LINK_FORMAT, response).sendToTarget()
}
else -> {
handler.obtainMessage(Msg.MESSAGE_PAR_FUNCTION, response).sendToTarget()
}
}
} catch (e: Exception) {
when (e) {
is ConnectException -> {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
}
is SocketTimeoutException -> {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
}
}
}
mJob?.cancelAndJoin()
}
}
}
}
Downloading a images:
class ThumbDownloader(dataAdapter: DeviceAdapter, data: ArrayList<MediaData>, file_path: String, swipe: SwipeRefreshLayout) : CoroutineScope {
private var job: Job = Job()
private var file_path: String
private var dataAdapter: DeviceAdapter
private var data: ArrayList<MediaData>
private var swipe: SwipeRefreshLayout
init {
this.data = data
this.file_path = file_path
this.dataAdapter = dataAdapter
this.swipe = swipe
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
fun cancel() {
job.cancel()
}
fun execute() = async {
var item: File? = null
for (i in data) {
val task = async(Dispatchers.IO) {
val url = i.fPath!!
val real_url = "${MainActivity.BaseUrl}$url"
item = NetworkUtil.downloadFile(real_url, file_path, false)
}
task.await()
if (item != null) {
dataAdapter.insertItem(i)
}
}
cancel()
swipe.isRefreshing = false
}
}
Any ideas how to come up with their synchronization while waiting for the previous commands to complete?
Hello I tried to make Coroutines Flow (using callbackFlow) and tried to convert it to live data, but it seems it's not updating.
You can see my code below:
#ExperimentalCoroutinesApi
suspend fun checkInDanger(): Flow<NetworkStatus<List<UserFire>>> = callbackFlow {
val check = fs.collection(UserFire.COLLECTION).whereEqualTo(UserFire.DANGER, true)
.addSnapshotListener { value, e ->
if (e != null) {
trySend(NetworkStatus.Failed("Error occurred\n${e.code}"))
return#addSnapshotListener
}
if (value == null || value.isEmpty) trySend(NetworkStatus.Empty)
else {
val users = value.map { it.toObject(UserFire::class.java) }
trySend(NetworkStatus.Success(users))
}
}
awaitClose { }
}.flowOn(Dispatchers.IO)
On my repositories:
#ExperimentalCoroutinesApi
override suspend fun checkInDanger(): Flow<Status<List<User>>> = flow {
when (val result = network.checkInDanger().first()) {
is NetworkStatus.Success -> emit(Status.Success(result.data.map {
MapVal.userFireToDom(it)
}))
is NetworkStatus.Empty -> emit(Status.Success(listOf<User>()))
is NetworkStatus.Failed -> emit(Status.Error(null, result.error))
}
}
In my ViewModel:
val checkInDanger = liveData(Dispatchers.IO) {
try {
useCase.checkInDanger().collectLatest {
emit(it)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
But when I changed the value in my Firebase, it's not fetching new data...
Anyone know why it's not fixed
I kind of find the way, but it's with callback, if we used callback, it can fetch data update even without live-data, but for my case I tried to push the callback to live data again,
so the code will be like this:
fun checkInDanger(networkStatus: (NetworkStatus<List<UserFire>>) -> Unit) {
fs.collection(UserFire.COLLECTION).whereEqualTo(UserFire.DANGER, true)
.addSnapshotListener { value, e ->
if (e != null) {
networkStatus(NetworkStatus.Failed("Error occurred\n${e.code}"))
return#addSnapshotListener
}
if (value == null || value.isEmpty) networkStatus(NetworkStatus.Empty)
else {
val users = value.map { it.toObject(UserFire::class.java) }
networkStatus(NetworkStatus.Success(users))
}
}
}
In my repositories:
override fun checkInDanger(callback: (Status<List<User>>) -> Unit) {
network.checkInDanger { result ->
when (result) {
is NetworkStatus.Success -> callback(Status.Success(result.data.map {
MapVal.userFireToDom(it)
}))
is NetworkStatus.Empty -> callback(Status.Success(listOf<User>()))
is NetworkStatus.Failed -> callback(Status.Error(null, result.error))
}
}
}
In my ViewModel:
fun checkInDanger(callback: (Status<List<User>>) -> Unit) = useCase.checkInDanger { callback(it) }
val setUsers = MutableLiveData<List<User>>().apply { this.value = listOf() }
val users: LiveData<List<User>> = setUsers
In my UI Class (Fragment Main):
val inDangerCallback: (Status<List<User>>) -> Unit = {
if (relative != null) {
when (it) {
is Status.Success ->
viewModel.setUsers.value =
it.data?.filter { user -> user.username in relative!!.pure }
else -> requireView().createSnackBar(it.error!!, 1000)
}
}
}
viewModel.checkInDanger(inDangerCallback)
viewModel.users.observe(viewLifecycleOwner) { users ->
println(users.size})
users?.forEach { user -> println(user.username) }
}
And the code can run perfectly and update automatically...
Trying to stick to the MVVM pattern rules. The final data should come to the ViewModel (I'm making an exception for Glide, but that's not the point).
Created several subcollections in the document (likes, comments, images, and so on).
After creating them, I ran into the problem of requesting the Firestore inside the main request. The data just doesn't have time to arrive.
How do I wait for data before "data.add" in such a situation?
ViewModel
init {
data = loadData()
}
fun getData(loadNextData: Boolean): LiveData<Response<List<Model>>> {
if (loadNextData)
data = loadData()
return data
}
private fun loadData(): LiveData<Response<List<Model>>> {
return liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Response.Loading)
val result = repository.loadData()
if (result is Response.Success || result is Response.Error)
emit(result)
}
}
Repository
suspend fun loadData(): Response<List<Model>> {
return suspendCoroutine { continuation ->
if (LAST_DOCUMENT == null) {
firebaseRepository.getReference(DEFAULT_COLLECTION_NAME, DEFAULT_ORDER_FIELD, DEFAULT_ORDER_DIRECTION)
} else {
firebaseRepository.getReference(DEFAULT_COLLECTION_NAME, DEFAULT_ORDER_FIELD, DEFAULT_ORDER_DIRECTION, LAST_DOCUMENT!!)
}
.get()
.addOnSuccessListener { query ->
try {
LAST_DOCUMENT = query.documents.lastOrNull()
query.documents.forEach { document ->
document.toObject(ModelDTO::class.java)?.let {
it.id = document.id
it.likes_count = // TODO
it.comments_count = // TODO
it.images_uri.add(//TODO)
data.add(it.mapToEntity())
}
}
continuation.resume(Response.Success(data))
} catch (exception: Exception) {
continuation.resume(Response.Error(exception))
}
}
.addOnFailureListener { exception ->
continuation.resume(Response.Error(exception))
}
}
}
I'm using WorkManager 1.0.0-alpha02
I've created my worker class like:
class PrintWorker: Worker(){
override fun doWork(): WorkerResult {
try {
val label: String = inputData.getString(LABEL_ARG_CONTENIDO, null)
val isLastLabel: Boolean = inputData.getBoolean(LABEL_ARG_IS_LAST,false)
var result = Printer(applicationContext).print(label)
var outPut: Data = Data.Builder().putString("PrinterResponse",result.printerResponse).putBoolean(LABEL_ARG_IS_LAST,isLastLabel).build()
outputData = outPut
return result.workResult
}catch (e: Exception){
return WorkerResult.FAILURE
}
}
companion object {
const val LABEL_ARG_CONTENIDO = "Label"
const val LABEL_ARG_IS_LAST = "Position"
}
}
and then in my viewmodel I've schedulled the work chain like:
var myQueue: WorkContinuation? = null
for (label in labelEntities){
val newPrintWork = OneTimeWorkRequest.Builder(PrintWorker::class.java)
val builder = Data.Builder()
var data: Data
builder.putString(PrintWorker.LABEL_ARG_CONTENIDO, label.contenido)
if(myQueue == null){
data = builder.build()
newPrintWork.setInputData(data)
myQueue = WorkManager.getInstance().beginUniqueWork(printWorkId,ExistingWorkPolicy.REPLACE,newPrintWork.build())
}
else{
if(labelEntities.indexOf(label) == labelEntities.size - 1)
builder.putBoolean(PrintWorker.LABEL_ARG_IS_LAST, true)
data = builder.build()
newPrintWork.setInputData(data)
myQueue.then(newPrintWork.build())
}
}
myQueue?.enqueue()
finally in another piece of code I'm observing it with:
viewmodel.printStatus.observe(this, Observer { works ->
if(works!= null){
if(works.filter { it.state == State.FAILED}.isNotEmpty()){
MyWorkManager().cancelPrinting()
context?.let { showDialog(MyAlertDialogManager(it).noMacAddressErrorDialog()) }
}
if(works.filter { it.state == State.SUCCEEDED }.size == works.size &&
works.isNotEmpty() &&
works.filter { it.outputData.getBoolean(LABEL_ARG_IS_LAST,false) }.isNotEmpty()){
context?.let { showDialog(MyAlertDialogManager(it).confirmPrintedCorrectly(factura,this)) }
}
}
})
The first work gets done right after enqueueing and returns Worker.WorkerResult.SUCCESS but the rest of the chain doesnt gets called