Android : Very large html(>25k lines) to pdf is not generated - android

I have an html that I want to be converted to a pdf.
The html is thousands of lines. I have observed that for less than 25k(more or less) lines of html code the generation to pdf is happening in about 2-3 seconds. When the html passes the above threshold then the html is never generated and the programm runs forever(I waited for 10 mins). Right now excuse me that I can't provide to you the exact number of lines of the above threshold because the html is produced randomly. I checked that the html is correct and I paste it to an html viewer and it worked.
At first for the generation of the pdf I used the classic one pdfConverter : https://github.com/blink22/react-native-html-to-pdf/blob/master/android/src/main/java/android/print/PdfConverter.java .
I modified the code in order to see what happened and I implement all the functions of the WebviewClient. Here is my modified code :
class PdfConverter private constructor() : Runnable {
private var mContext: Context? = null
private var mHtmlString: String? = null
private var mPdfFile: File? = null
private var mPdfPrintAttrs: PrintAttributes? = null
private var mIsCurrentlyConverting = false
private var mWebView: WebView? = null
var pdfcreator_observer :PdfCreator? = null
override fun run() {
mWebView = WebView(mContext as Context)
mWebView!!.webViewClient = object : WebViewClient() {
override fun onReceivedError (view: WebView,
request: WebResourceRequest,
error: WebResourceError
){
Log.d("michav/1","michav/onReceivedError")
}
override fun onReceivedHttpError (view: WebView,
request: WebResourceRequest,
errorResponse: WebResourceResponse
){
Log.d("michav/1","michav/onReceivedHttpError")
}
override fun onReceivedSslError(view: WebView,
handler: SslErrorHandler,
error: SslError
){
Log.d("michav/1","michav/onReceivedSslError")
}
override fun onRenderProcessGone(view: WebView, detail:RenderProcessGoneDetail):Boolean{
Log.d("michav/1", "michav/onRenderProcessGone")
return true
}
override fun doUpdateVisitedHistory( view: WebView, url:String, isReload:Boolean){
Log.d("michav/1", "michav/doUpdateVisitedHistory")
}
override fun onFormResubmission(view:WebView, dontResend:Message , resend:Message ){
Log.d("michav/1", "michav/onFormResubmission")
}
override fun onLoadResource(view:WebView, url:String){
Log.d("michav/1", "michav/onLoadResource")
}
override fun onPageCommitVisible(view:WebView, url:String){
Log.d("michav/1", "michav/onPageCommitVisible")
}
override fun onPageStarted(view:WebView, url:String, favicon:Bitmap ){
Log.d("michav/1", "michav/onPageStarted")
}
override fun onReceivedClientCertRequest(view:WebView, request:ClientCertRequest){
Log.d("michav/1", "michav/onReceivedClientCertRequest")
}
override fun onReceivedHttpAuthRequest(view:WebView, handler:HttpAuthHandler, host:String, realm:String){
Log.d("michav/1", "michav/onReceivedHttpAuthRequest")
}
override fun onReceivedLoginRequest(view:WebView, realm:String, account:String, args:String){
Log.d("michav/1", "michav/onReceivedLoginRequest")
}
override fun onSafeBrowsingHit(view:WebView, request:WebResourceRequest, threatType:Int, callback:SafeBrowsingResponse){
Log.d("michav/1", "michav/onSafeBrowsingHit")
}
override fun onScaleChanged(view:WebView, oldScale:Float, newScale:Float){
Log.d("michav/1", "michav/onScaleChanged")
}
override fun onTooManyRedirects(view:WebView, cancelMsg:Message, continueMsg:Message){
Log.d("michav/1", "michav/onTooManyRedirects")
}
override fun onUnhandledKeyEvent(view:WebView, event:KeyEvent){
Log.d("michav/1", "michav/onUnhandledKeyEvent")
}
override fun shouldInterceptRequest(view:WebView, request:WebResourceRequest):WebResourceResponse{
Log.d("michav/1", "michav/shouldInterceptRequest")
return WebResourceResponse("","",(1) as InputStream)
}
override fun shouldInterceptRequest(view:WebView, url:String):WebResourceResponse{
Log.d("michav/1", "michav/shouldInterceptRequest")
return WebResourceResponse("","",(1) as InputStream)
}
override fun shouldOverrideKeyEvent(view:WebView, event:KeyEvent):Boolean{
Log.d("michav/1", "michav/shouldOverrideKeyEvent")
return true
}
override fun shouldOverrideUrlLoading(view:WebView, request:WebResourceRequest):Boolean{
Log.d("michav/1", "michav/shouldOverrideUrlLoading")
return true
}
override fun shouldOverrideUrlLoading(view:WebView, url:String):Boolean{
Log.d("michav/1", "michav/shouldOverrideUrlLoading")
return true
}
override fun onPageFinished(view: WebView, url: String) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) throw RuntimeException(
"call requires API level 19"
) else {
val documentAdapter =
mWebView!!.createPrintDocumentAdapter()
documentAdapter.onLayout(
null,
pdfPrintAttrs,
null,
object : LayoutResultCallback() {},
null
)
documentAdapter.onWrite(
arrayOf(PageRange.ALL_PAGES),
outputFileDescriptor,
null,
object : WriteResultCallback() {
override fun onWriteFinished(pages: Array<PageRange>) {
destroy()
pdfcreator_observer?.update_from_pdfconverter()
}
})
}
}
}
mWebView!!.loadData(mHtmlString, "text/HTML", "UTF-8")
}
var pdfPrintAttrs: PrintAttributes?
get() = if (mPdfPrintAttrs != null) mPdfPrintAttrs else defaultPrintAttrs
set(printAttrs) {
mPdfPrintAttrs = printAttrs
}
fun convert(
context: Context?,
htmlString: String?,
file: File?
) {
requireNotNull(context) { "context can't be null" }
requireNotNull(htmlString) { "htmlString can't be null" }
requireNotNull(file) { "file can't be null" }
if (mIsCurrentlyConverting) return
mContext = context
mHtmlString = htmlString
mPdfFile = file
mIsCurrentlyConverting = true
runOnUiThread(this)
}
private val outputFileDescriptor: ParcelFileDescriptor?
private get() {
try {
mPdfFile!!.createNewFile()
return ParcelFileDescriptor.open(
mPdfFile,
ParcelFileDescriptor.MODE_TRUNCATE or ParcelFileDescriptor.MODE_READ_WRITE
)
} catch (e: Exception) {
Log.d(TAG, "Failed to open ParcelFileDescriptor", e)
}
return null
}
private val defaultPrintAttrs: PrintAttributes?
private get() = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) null else PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.NA_GOVT_LETTER)
.setResolution(Resolution("RESOLUTION_ID", "RESOLUTION_ID", 600, 600))
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build()
private fun runOnUiThread(runnable: Runnable) {
val handler = Handler(mContext!!.mainLooper)
handler.post(this)
}
private fun destroy() {
mContext = null
mHtmlString = null
mPdfFile = null
mPdfPrintAttrs = null
mIsCurrentlyConverting = false
mWebView = null
}
companion object {
private const val TAG = "PdfConverter"
private var sInstance: PdfConverter? = null
#get:Synchronized
val instance: PdfConverter?
get() {
if (sInstance == null) sInstance =
PdfConverter()
return sInstance
}
}
}
I call the above code with the following code
fun createPdfFromHtml(htmlstring: String) {
val directory = File(directory_whole_path)
if (!directory.exists()) {
directory.mkdir()
Toast.makeText(
m_context,
"The directory $directory_whole_path created",
Toast.LENGTH_SHORT
).show()
}
var converter: PdfConverter? = PdfConverter.instance
val file = File(
directory_whole_path,
nameofpdf
)
converter?.pdfcreator_observer = this
converter?.convert(m_context, htmlstring, file)
mFilepdf = file
}
None of the debugging logs in pdfConverter is called. I try to know what is happening and when I have the large html the code runs forever. Do you suggest to me to change the html conversion procedure ?
EDIT
I changed the code as the first answer :
val directory = File(directory_whole_path)
if (!directory.exists()) {
directory.mkdir()
Toast.makeText(
m_context,
"The directory $directory_whole_path created",
Toast.LENGTH_SHORT
).show()
}
var this_to_pass =this
GlobalScope.launch(Dispatchers.Default ){
var converter: PdfConverter? = PdfConverter.instance
val file = File(
directory_whole_path,
nameofpdf
)
converter?.pdfcreator_observer = this_to_pass
converter?.convert(m_context, htmlstring, file)
mFilepdf = file
}
But also nothing worked ... still the problem occurs ... when the pdf is a little bit larger than 25k lines of html code the programm runs forever. For this attempt I also put in comment all the webviewclient overrides except the on pagefinished.
Also in the "run" of Android Studio the below is displayed :
W/chromium: [WARNING:navigation_controller_impl.cc(2579)] Refusing to load URL as it exceeds 2097152 characters.

Your conversion code seems to be running on the UI thread, and that's a problem. Please make sure you move the work to a background thread. There are a ton of possibilities for that: kotlin coroutines, IntentService, HandlerThread, AsyncTask, etc etc
Since your converter is a runnable, the easiest way would be to create a HandlerThread, and instead of calling your converter from the main thread
val handler = Handler(mContext!!.mainLooper)
handler.post(this)
call it from your newly created handlerThread
val handler = Handler(handlerThread.looper)
handler.post(this)
Also, be sure to call handlerThread.quit() in your activity/fragment's onDestroy!

I had to change the below line in the PdfConverter :
mWebView!!.loadData(mHtmlString, "text/HTML", "UTF-8")
to
mWebView!!.loadDataWithBaseURL(
"file:///android_asset/",
mHtmlString,
"text/html",
"UTF-8",
""
)
With this command I can get html-to-pdf-conversion without limit to the characters

Related

onPageStarted/onPageFinished not updating view and crash

I have an activity where I setup an animated AnimatedVector on an ImageView then I am loading an url in a WebView, everythings good right here.
The issue is in onPageStarted webview client callback, I got a crash because binding.loader.drawable return null so the cast is impossible.
I can't figure it out why the drawable is null here !
Second issue is (if i comment the line in onPageStarted) in onPageFinished, the two visibility of my views I try to set does nothing at all, they are still visibles.
Spoiler : Of course the app crash right after when trying to get the drawable and cast it
Have you already face this issue ?
class ViewRecipeActivity : AppCompatActivity() {
private val binding by viewBinding(ActivityViewRecipeBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setupUI()
}
//region setup UI
private fun setupUI() {
setUpLoader()
setupWebView()
}
private fun setUpLoader() {
with(binding.loader) {
val drawable = AnimatedVectorDrawableCompat.create(this#ViewRecipeActivity, R.drawable.animated_loader)
setImageDrawable(drawable)
}
}
private fun setupWebView() {
val client = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
(binding.loader.drawable as Animatable).start() //Crash here because drawable is null
}
override fun onPageFinished(view: WebView?, url: String?) {
binding.loader.visibility = View.GONE
binding.loaderBackground.visibility = View.GONE
(binding.loader.drawable as Animatable).stop()
}
}
with(binding.recipeView) {
webViewClient = client
}
val recipeUrl = intent.extras?.getString(RECIPE_URL_EXTRA)
if(recipeUrl == null) {
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
} else {
binding.recipeView.loadUrl(recipeUrl)
}
}
//endregion
companion object {
const val RECIPE_URL_EXTRA = "recipe_url_extra"
}
}

how to close jitsi call activity when any user left Conference android kotlin

I was developing some Conference application and got confused on how to handle PARTICIPANT_LEFT.action in code. Here is my code
private var roomName: String? = null
private lateinit var anotherUser:String
private var username: String? = SharedUserObject.userID
private var jitsiMeetView: JitsiMeetView? = null
#SuppressLint("LogNotTimber")
#Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_call)
anotherUser = intent.getStringExtra("AnotherUser").toString()
SharedUserObject.checkCallActivity = true
// get data from Two Activities (IncomingCall , CallFrom )
// create jitsi call view
// if (!SharedUserObject.checkCallActivity) {
dataFromIntent
jitsiMeetView = JitsiMeetView(this#CallActivity)
val conferenceOptions = videoChatOptions
jitsiMeetView!!.join(conferenceOptions)
setContentView(jitsiMeetView)
jitsiMeetView!!.listener = this
// }
val intentFilter = IntentFilter()
intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_LEFT.action)
LocalBroadcastManager.getInstance(this).registerReceiver(BroadcastReceiver(applicationContext), intentFilter)
}
override fun requestPermissions(
strings: Array<String>,
i: Int,
permissionListener: PermissionListener
) {}
// this run when user accept the call and set it busy
#SuppressLint("LogNotTimber")
override fun onConferenceJoined(map: Map<String, Any>) {
SharedUserObject.inCall = true
}
#SuppressLint("LogNotTimber")
// this run when user end the call and set it not busy
override fun onConferenceTerminated(map: Map<String, Any>) {
SharedUserObject.inCall = false
//
SharedUserObject.checkCallActivity = false
jitsiMeetView?.leave()
jitsiMeetView?.dispose()
jitsiMeetView = null
finish()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
override fun onConferenceWillJoin(map: Map<String, Any>) {
//LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(muteBroadcastIntent)
}
private val dataFromIntent: Unit
get() {
roomName = intent.getStringExtra("RoomName").toString()
username = ""
}
// Set call subject here. Connection with jitsi call server and create Call.
private val videoChatOptions: JitsiMeetConferenceOptions
get() {
var videoChatUrl: URL? = null
try {
videoChatUrl = URL("https://meet.jit.si")
} catch (e: Exception) {
e.printStackTrace()
}
val meetUserInfo = JitsiMeetUserInfo()
meetUserInfo.displayName = SharedUserObject.userID
return JitsiMeetConferenceOptions.Builder()
.setServerURL(videoChatUrl)
.setAudioOnly(true)
.setAudioMuted(false)
.setUserInfo(meetUserInfo)
.setSubject(roomName) // Set call subject here. use to display phone number here.
.setRoom(roomName)
.build()
}
override fun onDestroy() {
super.onDestroy()
SharedUserObject.checkCallActivity = false
jitsiMeetView?.leave()
jitsiMeetView?.dispose()
jitsiMeetView = null
JitsiMeetActivityDelegate.onHostDestroy(this)
}
}
I need that when user leaves the Conference then the Conference should end. I tried using BroadcastEvent.Type.PARTICIPANT_LEFT.action
but no success was received. Please guide me if there is some other way to implement it.

How to change subtile dynamically on PlaybackTransportControlGlue android TV

I am building a video player app for Android TV. I am using Exoplayer leanback dependency as explained in https://developer.android.com/training/tv/playback/transport-controls.
So far I've been able to display the video title, which is static, but I need to display a subtitle that is dynamic, it changes whenever the video playing changes. How can I do it?
The image below shows how the video player looks like. I've used a subtitle phrase as a placeholder on where it should appear.
I was able to solve the problem. I added a listener in the VideoSupportFragment class, Player.EventListener.
This way:
class VideoFragment(mediaItems: Map<String, Any>) : VideoSupportFragment(), Player.EventListener {
private var playerAdapter: ExoPlayerAdapter? = null
private var _mediaItems: Map<String, Any>? = null
private lateinit var mMediaPlayerGlue: VideoMediaPlayerGlue<ExoPlayerAdapter>
private var mItems: List<*>? = null
init {
_mediaItems = mediaItems
}
private val mHost: VideoSupportFragmentGlueHost = VideoSupportFragmentGlueHost(this)
private fun playWhenReady(glue: PlaybackGlue) {
if (glue.isPrepared) {
glue.play()
} else {
glue.addPlayerCallback(object : PlaybackGlue.PlayerCallback() {
override fun onPreparedStateChanged(glue: PlaybackGlue) {
if (glue.isPrepared) {
glue.removePlayerCallback(this);
glue.play()
}
}
})
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val mediaSource = _mediaItems?.get("media_source") as Map<*, *>
playerAdapter = ExoPlayerAdapter(activity!!, _mediaItems!!)
mMediaPlayerGlue =
VideoMediaPlayerGlue(activity, playerAdapter!!)
mMediaPlayerGlue.host = mHost;
mMediaPlayerGlue.isControlsOverlayAutoHideEnabled = true
mItems = mediaSource["media_items"] as List<*>
mMediaPlayerGlue.title = mediaSource["title"] as CharSequence?
mMediaPlayerGlue.playerAdapter.setDataSource()
mMediaPlayerGlue.isSeekEnabled = true
playerAdapter?.player?.addListener(this);
playWhenReady(mMediaPlayerGlue)
}
override fun onPause() {
super.onPause()
playerAdapter?.player?.pause()
}
override fun onResume() {
super.onResume()
playerAdapter?.player?.play()
}
override fun onDestroy() {
super.onDestroy()
playerAdapter?.player?.removeListener(this);
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (mItems?.size!! > 1){
val item : Map<*, *> = mItems!![playerAdapter?.player?.currentWindowIndex!!] as Map<*, *>
mMediaPlayerGlue.subtitle = item["subtitle"] as String
}
}
}

How to return response value from coroutine

I've recently work with Kotlin, and got really stuck with this one problem. I'm trying to return float value receive onResponse of a coroutine api call function. I'm trying to create a class that handle api call and use it on a fragment.
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
....
func getBalance(cardNo: String): Float?{
val cardBalance: Float = null
GlobalScope.launch(Dispatchers.Main) {
val cardDetails = cardApi.getCardBalance(cardNo)
cardDetails.enqueue(object : Callback<Card> {
override fun onFailure(call: Call<Card>, t: Throwable) {
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
}
override fun onResponse(call: Call<Card>, response: Response<Card>) {
if (response.isSuccessful) {
val card = response.body()!!
cardBalance = card.cardAvailableBalance
} else {
val error: ApiError = ErrorUtils.parseError(response)
val message = error.code + error.message
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
context!!.toast("Errror: " + message)
promptErrorDialog(error)
}
}
})
}}
return cardBalance
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
val galA = 10.5f
val galB = "Test"
private var pass = FunctionA(context!!, valA ,valB)
....
val point = "sasd12125"
private fun gooToo(){
val B = pass.getBalance(point)
print("TEST")
println("value B: " + B)
}
....
}
What happend right now, since the coroutine will take some time in background, val B are null and didn't get the value obtained onResponse. Only after I try to call that functionA again, then the value are updated. I'm not sure if I'm doing it right and I've tried to search for solutions, but it doesn't suit with my current situation. Probably my searching skill are soo bad.
Output
TEST
value B: null
How should I wait for the coroutine to finish before return the cardBalance value?
Proper way to return a single value from a coroutine is to use await().
Now, since you use coroutine to wrap some callback API, that wouldn't work so well. So I would suggest to go with something like this:
val scope = CoroutineScope(Dispatchers.IO)
suspend fun getBalance(cardNo: String): Float{
val res = CompletableDeferred<Float>()
scope.launch {
val cardDetails = cardApi.getCardBalance(cardNo)
cardDetails.enqueue(object : Callback<Card> {
override fun onFailure(call: Call<Card>, t: Throwable) {
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
}
override fun onResponse(call: Call<Card>, response: Response<Card>) {
if (response.isSuccessful) {
val card = response.body()!!
res.complete(card.cardAvailableBalance)
} else {
val error: ApiError = ErrorUtils.parseError(response)
val message = error.code + error.message
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
res.completeExceptionally(message)
withContext(Dispatchers.Main) {
promptErrorDialog(error)
}
}
}
})
}
return res.await()
}
A few points to consider. First, I used Dispatchers.IO instead of Dispatchers.Main, and switch to Main thread only when needed using withContext(Dispatchers.Main). Otherwise, you're just running your IO on the main thread, coroutine or not.
Second, using GlobalScope is a bad practice, and you should avoid it at all cost. Instead I create a custom scope that you can .cancel() to prevent coroutine leak.
Third, the most correct way would be to return Deferred<Float>, and not Float, since await() is blocking. But I left it for simplicity.
To solve my little problem I end up using a callback to pass the response data. I found that this method work perfectly and easier to understand for my level of understanding. This method skeleton can also be reused for any api service call that I want to use in the future.
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
private var card: Card? = null
interface CardBalanceCallback {
fun processFinish(output: Boolean, cardBalance: Float?)
}
fun getCardBalance(cardNo: String, callback: CardBalanceCallback) = runBlocking {
getBalance(cardNo, callback)
}
private fun getBalance(cardNo: String, callback: CardBalanceCallback) = CoroutineScope(Dispatchers.Main).launch {
try {
val response = cardApi.getCardBalance(cardNo).await()
if (response.isSuccessful) {
card = response.body()
callback.processFinish(true, card!!.cardAvailableBalance)
} else {
callback.processFinish(false, null)
val error: ApiError = ErrorUtils.parseError(response)
val message = when {
error.error.code.isNotEmpty() -> error.error.code + error.error.message
else -> error.code + error.message
}
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
promptErrorDialog(error)
}
} catch (e: HttpException) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast(e.message.toString())
} catch (e: Throwable) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast( e.message.toString())
}
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
private var funcService = FunctionA(null, null ,null)
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity!!.application as App).component.inject(this)
val valA = 10.5f
val valB = "Test"
val cardNo = "4001526976443264"
val cardExpDate = "1119"
funcService = FunctionA(context!!, valA ,valB)
getCardBalanceApi(cardNo, cardExpDate)
}
....
private fun getCardBalanceApi(cardNo: String, cardExpDate: String?) {
showLoadingDialog()
funcService.getCardBalance(cardNo, object : SmartPayService.CardBalanceCallback {
override fun processFinish(output: Boolean, cardBalance: Float?) {
dismissLoadingDialog()
if (cardBalance != null) {
checkBalance(cardNo, cardBalance, cardExpDate)
}
}
})
}
....
}
This is some simple changes that I made for this particular problem on my first post. This approach might not be as good or smooth enough as I'm still learning. Hope it help some of you guys. cheers
Make getBalance() a suspend function and then call using lifecycleScope in your fragment
private fun gooToo(){
lifecycleScope.launch {
val B = pass.getBalance(point)
print("TEST")
println("value B: " + B)
}
}
getBalance() function signature would be something like
suspend fun getBalance(): Float = withContext(Dispatchers.IO)

PostValue didn't update my Observer in MVVM

I have an activity to perform rest API everytime it opened and i use MVVM pattern for this project. But with this snippet code i failed to get updated everytime i open activity. So i debug all my parameters in every line, they all fine the suspect problem might when apiService.readNewsAsync(param1,param2) execute, my postValue did not update my resulRead parameter. There were no crash here, but i got result which not updated from result (postValue). Can someone explain to me why this happened?
Here what activity looks like
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityReadBinding>(this,
R.layout.activity_read).apply {
this.viewModel = readViewModel
this.lifecycleOwner = this#ReadActivity
}
readViewModel.observerRead.observe(this, Observer {
val sukses = it.isSuccess
when{
sukses -> {
val data = it.data as Read
val article = data.article
//Log.d("-->", "${article.toString()}")
}
else -> {
toast("ada error ${it.msg}")
Timber.d("ERROR : ${it.msg}")
}
}
})
readViewModel.getReadNews()
}
Viewmodel
var observerRead = MutableLiveData<AppResponse>()
init {
observerRead = readRepository.observerReadNews()
}
fun getReadNews() {
// kanal and guid i fetch from intent and these value are valid
loadingVisibility = View.VISIBLE
val ok = readRepository.getReadNews(kanal!!, guid!!)
if(ok){
loadingVisibility = View.GONE
}
}
REPOSITORY
class ReadRepositoryImpl private constructor(private val newsdataDao: NewsdataDao) : ReadRepository{
override fun observerReadNews(): MutableLiveData<AppResponse> {
return newsdataDao.resultRead
}
override fun getReadNews(channel: String, guid: Int) = newsdataDao.readNews(channel, guid)
companion object{
#Volatile private var instance: ReadRepositoryImpl? = null
fun getInstance(newsdataDao: NewsdataDao) = instance ?: synchronized(this){
instance ?: ReadRepositoryImpl(newsdataDao).also {
instance = it
}
}
}
}
MODEL / DATA SOURCE
class NewsdataDao {
private val apiService = ApiClient.getClient().create(ApiService::class.java)
var resultRead = MutableLiveData<AppResponse>()
fun readNews(channel: String, guid: Int): Boolean{
GlobalScope.launch {
val response = apiService.readNewsAsync(Constants.API_TOKEN, channel, guid.toString()).await()
when{
response.isSuccessful -> {
val res = response.body()
val appRes = AppResponse(true, "ok", res!!)
resultRead.postValue(appRes)
}
else -> {
val appRes = AppResponse(false, "Error: ${response.message()}", null)
resultRead.postValue(appRes)
}
}
}
return true
}
}
Perhaps this activity is not getting stopped.
Check this out:
When you call readViewModel.getReadNews() in onCreate() your activity is created once, only if onStop is called will it be created again.

Categories

Resources