4

Using a simple RxKotlin Single, I'm receiving either a android.view.ViewRootImpl$CalledFromWrongThreadException exception, or by adding .observeOn(AndroidSchedulers.mainThread()), I'm getting a NetworkOnMainThread exception.

fun loadStaffCalendar() {
        var calendarParser = CalendarParser()
        calendarParser.getSingleBearCal()
            .subscribeOn(Schedulers.io())
            .subscribeBy(
                onError ={error("Error loading calendar\n${it.message}")},
                onSuccess = { responseBody ->
                        println("ResponseBody retrieved")
                        var staffList = calendarParser.parseStringIntoSchedule(responseBody.string())
                        view.loadToAdapter(staffList)
                         println(staffList)

                }

            )

I can get the staffList to print in console, but as soon as I try to load it into the View's adapter, it crashes with an CalledFromWrongThread exception.

So here's the crash when I add .observeOn(AndroidSchedulers.mainThread()):

io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | android.os.NetworkOnMainThreadException
        at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367)
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:126)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: android.os.NetworkOnMainThreadException
        at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513)
        at com.android.org.conscrypt.Platform.blockGuardOnNetwork(Platform.java:415)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:527)
        at okio.InputStreamSource.read(Okio.kt:102) 

No additional network calls are made anywhere. Here's the rest:

class CalendarParser : AnkoLogger {
    fun getSingleBearCal(): Single<ResponseBody> {
        val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl("https://www.brownbearsw.com/")
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
        val bearApi: BearApi = retrofit.create(BearApi::class.java)

        return bearApi.file
    }

    fun parseStringIntoSchedule(wholeSchedule: String): ArrayList<StaffModel> {
        var dateMap: HashMap<LocalDate, String> = HashMap()
        var endDelim = "END:VEVENT"
        var events: List<String> = wholeSchedule.split(endDelim)
        var parsedStaffCal: ArrayList<StaffModel> = ArrayList()
        var today = LocalDate.now()
        // :: Pull event date from event data, pull staff list from "SUMMARY" line :: //
        events.forEach {
            var tempString = (it.substringAfterLast("DATE:", "FAIL").take(8))
            var dateTime: LocalDate = eightIntoDateTime(tempString)

            var summary: String = it.substringAfter("SUMMARY:", "FAIL")
                .let { it.substringBefore("UID").replace("\\n", "\n") }
            dateMap.put(dateTime, summary)
        }

        // ::Filter out all days before today:: //
        dateMap.forEach {
            if (!it.key.isBefore(today)) {
                val staffModel = StaffModel(it.key, it.value)
                parsedStaffCal.add(staffModel)
            }
        }
        //:: Sort chronologically :://
        parsedStaffCal.sortBy { it.localDate }

        return parsedStaffCal
    }

    fun eightIntoDateTime(s: String): LocalDate {
        return if (s.length == 8 && s.isDigitsOnly()) { // <-=-=-=-=-=- avoid potential formatting exceptions
            val dateString = ("${s.subSequence(0, 4)}-${s.subSequence(4, 6)}-${s.subSequence(6, 8)}")
            LocalDate.parse(dateString)
        } else LocalDate.parse("1999-12-31")
    }  

Retrofit API:

package com.pkg.archdemo.schedule;

import io.reactivex.Single;
import okhttp3.ResponseBody;
import retrofit2.http.GET;
import retrofit2.http.Streaming;

public interface BearApi {
    @Streaming
    @GET("url.goes.here.ics")
    Single<ResponseBody> getFile();
}
PatchesDK
  • 102
  • 2
  • 10
  • 1
    You're missing the observeOn(AndroidSchedulers.mainThread and you are probably also missing the Observable.fromCallable in place of an Observable.just (or single, in this case single) – EpicPandaForce Aug 03 '19 at 23:51
  • " or by adding .observeOn(AndroidSchedulers.mainThread()), I'm getting a NetworkOnMainThread exception. " Adding the observeOn operator before or after subscribeOn is causing an exception. The Single is generated by my Retrofit API. Is that incomplete? – PatchesDK Aug 04 '19 at 00:00
  • Add `.observeOn(AndroidSchedulers.mainThread())` and make sure that your logic inside `onSuccess` doesn't make network calls – y.allam Aug 04 '19 at 09:38

2 Answers2

2

subscribeOn tells the observerable where to perform the work on, then observeOn is where the result of this work will be returned to. In your case, you need :

calendarParser.getSingleBearCal()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread()). 
            ......
a_local_nobody
  • 7,947
  • 5
  • 29
  • 51
  • Thanks for the follow up, I've updated the OP to best describe the problem. I've added Android .observeOn(AndroidSchedulers.mainThread()) both before and after the subscribeOn operator with no success. It's not my first day with RxJava, but it is my first experience with RxKotlin, and I've never seen this before working with RxJava – PatchesDK Aug 04 '19 at 16:18
0

There was an extraneous RxJava dependency that was conflicting with the RxKotlin dependency, I think. Removing it fixed the problem. I also took some of the work from onSuccess and added an operator, which is probably better practice anyway:

fun loadStaffCalendar() {
        var calendarParser = CalendarParser()
        calendarParser.getSingleBearCal()
            .subscribeOn(Schedulers.io())
            .map { calendarParser.parseStringIntoSchedule(it.string())  }
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onError = {error(it.localizedMessage.toString())},
                onSuccess = {view.loadToAdapter(it)})
    }

Gradle looks like:

implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
PatchesDK
  • 102
  • 2
  • 10