You are getting the class cast exception because you are trying to cast a String to a SpannableStringBuilder.
var htmlText:SpannableStringBuilder = source as SpannableStringBuilder
The way to get a string into a SpannableStringBuilder is
var htmlText = SpannableStringBuilder(source)
Regarding HTML span tag support for APIs below 24, I was thinking that HtmlCompat would provide span tag support for API 24 features, but it doesn't. That means that we have to process the span tag ourselves. (Since the span tag is not supported below API 24, we might consider using a HTML tag handler. Unfortunately, the tag handler is alerted to the presence of the span tag, but the span attributes are not made available to the tag handler :-(, so we have to do the following.)
The sample code below will process the background-color
attribute of the span tag for API 18+. (It may support APIs below 18 as well.) The approach is to use a regular expression to extract the appropriate span attribute values and text from the HTML and to convert them to Android text spans in a spanned string. The spanned string can then be set to a TextView. You can find explanatory comments in the code.
Here is what the screen looks like on an emulator running API 18. The highlights were created by the code below while the bolded text was created by a call to Html.fromHtml().

MainActivity.kt
class MainActivity : AppCompatActivity() {
// Html.fromHTML() seems to be a little picky about how HTML attributes are delimited.
// Mind the spaces!
private val mHtmlString =
" <span style=\"color: #FFFFFF ; background-color: #FF8983\">Highlighted</span><b> Bold!</b> Not bold <span style=\"background-color: #00FF00\">string</span>"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val tv = findViewById<TextView>(R.id.textView)
tv.text = processHtml(mHtmlString)
}
private fun processHtml(s: String): Spanned? {
// Easy for API 24+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return HtmlCompat.fromHtml(s, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
// HtmlCompat.fromHtml() for API 24+ can handle more <span> attributes than we try to here.
// We will just process the background-color attribute.
// HtmlCompat.fromHtml() will remove the spans in our string. Escape them before processing.
var escapedSpans = s.replace("<span ", "<span ", true)
escapedSpans = escapedSpans.replace("</span>", "</span>", true)
// Process all the non-span tags the are supported pre-API 24.
val spanned = HtmlCompat.fromHtml(escapedSpans, HtmlCompat.FROM_HTML_MODE_LEGACY)
// Process HTML spans. Identify each background-color attribute and replace the effected
// text with a BackgroundColorSpan. Here we assume that the background color is a hex number
// starting with "#". Other value such as named colors can be handled with additional
// processing.
val sb = SpannableStringBuilder(spanned)
val m: Matcher = SPAN_PATTERN.matcher(sb)
do {
if (m.find()) {
val regionEnd = m.start(0) + m.group(2).length
sb.replace(m.start(0), m.end(0), m.group(2))
.setSpan(
BackgroundColorSpan(parseInt(m.group(1), 16) or HTML_COLOR_OPAQUE_MASK),
m.start(0),
regionEnd,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
m.reset(sb)
m.region(regionEnd, sb.length)
}
} while (!m.hitEnd())
return sb
}
companion object {
val SPAN_PATTERN: Pattern =
Pattern.compile("<span.*?background(?:-color)?:\\s*?#([^,]*?)[\\s;\"].*?>(.*?)</span>")
const val HTML_COLOR_OPAQUE_MASK = 0xFF000000.toInt()
}
}