Android: Kotlin getting data from callback and emit with flow - android

Everyone, I have this method and want to emit listmessage but list is unable to get the list of values, How can I do it?
fun ReadMessage(dialog: QBChatDialog) = flow{
try {
var list: ArrayList<QBChatMessage> = ArrayList()
chatHelper.ReadChatHistory(dialog, object : QBEntityCallback<ArrayList<QBChatMessage>>{
override fun onSuccess(listmessage: ArrayList<QBChatMessage>?, p1: Bundle?) {
Log.e(TAG, "Reading Message: $p0")
}
override fun onError(p0: QBResponseException?) {
Log.e(TAG, "Reading Message Exception: $p0")
}
})
Log.e(TAG, "Reading Messageeeeeeeeee: $list")
emit(list)
}catch (e: Exception){
Log.e(TAG, "Reading Message Exceptionn: $e")
}
}

You should use CompletableDeferred. You can do something like this:
fun readMessage(dialog: QBChatDialog): Flow<ArrayList<QBChatMessage>> {
val historyDeferred = CompletableDeferred<ArrayList<QBChatMessage>>()
chatHelper.ReadChatHistory(dialog, object : QBEntityCallback<ArrayList<QBChatMessage>> {
override fun onSuccess(listmessage: ArrayList<QBChatMessage>?, p1: Bundle?) {
historyDeferred.complete(listmessage ?: arrayListOf())
}
override fun onError(p0: QBResponseException?) {
historyDeferred.completeExceptionally(p0 ?: CancellationException())
}
})
return flow {
try {
emit(historyDeferred.await())
} catch (e: Exception) {
Log.e(TAG, "Reading Message Exceptionn: $e")
}
}
}

Related

Live Data Observer called only once _ Android

Live Data Observer called only once. It is not updating the data from server when api is called again to update UI.
Here is my activity:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onClick(){
callAPI(binding.edtEMail.text.toString(), binding.edtPasswords.text.toString())
}
}
private fun callAPI(userName: String, password: String) {
var factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LoginViewModel(
networkAvailable,
application,
getLoginUseCase,
userName,
password
) as T
}
}
val loginViewModel: LoginViewModel by lazy {
ViewModelProvider(this, factory)[LoginViewModel::class.java]
}
loginViewModel.loginMainEntity.observe(this, Observer {
when (it) {
is Resource.Success -> {
it.data?.let { it ->
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}
is Resource.Error -> {
it.message?.let { it ->
when (it) {
getString(R.string.invalid_login) -> {
Toast.makeText(this, R.string.error_user_pass, Toast.LENGTH_LONG)
.show()
}
getString(R.string.service_not_available) -> {
Toast.makeText(
this,
R.string.error_service_not_available,
Toast.LENGTH_LONG
)
.show()
}
else -> {
Toast.makeText(this, it, Toast.LENGTH_LONG)
.show()
}
}
}
}
}
})
}
}
And here is LoginViewModel:
class LoginViewModel constructor
(
private val networkAvailable: NetworkAvailable,
private val app: Application,
private val getLoginUseCase: GetLoginUseCase,
private val userName: String,
private val password: String
) : ViewModel() {
private val _loginMainEntity = MutableLiveData<Resource<LoginMainEntity>>()
val loginMainEntity: LiveData<Resource<LoginMainEntity>>
get() = _loginMainEntity
init {
loginValues()
}
private fun loginValues() {
viewModelScope.launch {
try {
_loginMainEntity.postValue(Resource.Loading())
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.unknown)))
}
try {
if (networkAvailable.isNetworkConnected()) {
try {
_loginMainEntity.postValue(getLoginUseCase.execute(userName, password))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
}
} else if (!networkAvailable.isNetworkConnected()) {
try {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api_network)))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api_network)))
}
}
} catch (e: Exception) {
try {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
} catch (e: Exception) {
_loginMainEntity.postValue(Resource.Error(app.resources.getString(R.string.error_api)))
}
}
}
}
}

Get Realtime Database value to ArrayList using MVVM + Coroutines

I wanted to get a list of data from the realtime database using coroutines and MVVM and put them to recyclerview. It runs but the data from the realtime database are added after the recyclerview.adapter initialization, thus returning list.size to 0
ViewModel.kt
class DasarhukumdetailsViewModel : ViewModel() {
val database = FirebaseDatabase.getInstance().reference
var dasarHukumList = ArrayList<DasarHukum>()
fun getDHData(KEYVALUE: String?) = liveData(Dispatchers.Main.immediate) {
val postListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snapshot in snapshot.children) {
val res = snapshot.getValue(DasarHukum::class.java)
Log.d("dataAdd", "Adding: ${res?.filename}")
dasarHukumList.add(res!!)
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("readDHList", "loadPost:onCancelled", databaseError.toException())
throw databaseError.toException()
}
}
try {
if (KEYVALUE != null) {
database.child("dasarhukum").child(KEYVALUE).addValueEventListener(postListener)
}
emit(Resource.success(dasarHukumList))
} catch (e: Exception) {
emit(Resource.error(
null,
e.message ?: "Unknown Error"
))
}
}
Fragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
[...]
observerSetup(KEYVALUE)
rvSetup()
return binding.root
}
fun observerSetup(keyvalue: String?) {
viewModel.getDHData(keyvalue).observe(viewLifecycleOwner, {
when (it.status) {
Status.SUCCESS -> {
it?.data.let { dhList ->
dasarHukumAdapter.dasarhukumList = dhList
dasarHukumAdapter.notifyDataSetChanged()
}
}
Status.ERROR -> {
Toast.makeText(context, "Error getting documents: ${it.message}", Toast.LENGTH_LONG)
Log.e("realDB", it.message!!)
}
}
})
}
fun rvSetup() {
with(binding.rvDasarHukum) {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
adapter = dasarHukumAdapter
}
}
RVAdapter.kt
class DasarHukumAdapter : RecyclerView.Adapter<DasarHukumAdapter.DasarHukumViewHolder>() {
var dasarhukumList: List<DasarHukum>? = null
set(value) {
notifyDataSetChanged()
field = value
}
class DasarHukumViewHolder(private val binding: ItemDasarhukumBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(dasarHukum: DasarHukum?) {
binding.dasarhukum = dasarHukum
binding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DasarHukumViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemDasarhukumBinding.inflate(layoutInflater, parent, false)
return DasarHukumViewHolder(binding)
}
override fun onBindViewHolder(holder: DasarHukumViewHolder, position: Int) {
val dasarHukum = dasarhukumList?.get(position)
Log.d("dhVH", "Adding: ${dasarHukum?.name}")
holder.bind(dasarHukum)
}
override fun getItemCount(): Int {
Log.d("dhCount", "List size: ${dasarhukumList?.size}")
return dasarhukumList?.size ?: 0
}
How can the recyclerview waits for the viewmodel.getDHData() to returns the arraylist first then initialize so it can be displayed to the recyclerview?
By the time you are trying to emit the result using the following line of code:
emit(Resource.success(dasarHukumList))
The data hasn't finished loading yet, hence the zero size of the list. Firebase API is asynchronous, so you need to wait for the data in order to use it in another operation. So any code that needs data from the Realtime Database needs to be inside the "onDataChange()" method, or be called from there. So the simplest solution, in this case, would be to move the logic regarding the emitting the result, inside the callback:
fun getDHData(KEYVALUE: String?) = liveData(Dispatchers.Main.immediate) {
val postListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (snapshot in snapshot.children) {
val res = snapshot.getValue(DasarHukum::class.java)
Log.d("dataAdd", "Adding: ${res?.filename}")
dasarHukumList.add(res!!)
}
try {
if (KEYVALUE != null) {
database.child("dasarhukum").child(KEYVALUE).addValueEventListener(postListener)
}
emit(Resource.success(dasarHukumList))
} catch (e: Exception) {
emit(Resource.error(
null,
e.message ?: "Unknown Error"))
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w("readDHList", "loadPost:onCancelled", databaseError.toException())
throw databaseError.toException()
}
}
}

Nested Callback function with coroutines

I want to get a response from callback function async/await style of javascript using kotlin coroutines.
Here is my callback functions
offlineCatalog.findOfflineVideoById(id, object : OfflineCallback<Video> {
override fun onSuccess(video: Video?) {
video?.let {
//Return This Video
} ?: kotlin.run {
findVideoOnline(id, state)
}
}
override fun onFailure(throwable: Throwable?) {
findVideoOnline(id, state)
}
})
onlineCatalog.findVideoByID(id, object : VideoListener() {
override fun onVideo(video: Video?) {
video?.let {
//Return This Video
} ?: kotlin.run {
Log.e("Return Error")
}
}
override fun onError(errors: MutableList<CatalogError>) {
super.onError(errors)
Log.e("Return Error")
}
})
I want to call function that will return video object from OfflineCatalog if error in OfflineCatalog then search from OnlineCatalog.
such as
try{
val video:Video? = getVideo(id:String)
//do something
}catch(throwable:Throwable){
Log.e("Video not found")
}
Update: My Implementation
this is what I came up with
suspend fun getVideo(id: String): Video? = withContext(Dispatchers.IO) {
var video = getVideoOffline(id)
video?.let { video } ?: kotlin.run { getVideoOnline(id) }
}
suspend fun getVideoOffline(id: String): Video? = suspendCancellableCoroutine { cont ->
(offlineCatalog.findOfflineVideoById(id, object : OfflineCallback<Video> {
override fun onSuccess(video: Video?) = cont.resume(video)
override fun onFailure(throwable: Throwable?) = cont.resume(null)
}))
}
suspend fun getVideoOnline(id: String): Video? = suspendCancellableCoroutine { cont ->
catalog.findVideoByID(id, object : VideoListener() {
override fun onVideo(video: Video?) = cont.resume(video)
override fun onError(errors: MutableList<CatalogError>) = cont.resume(null)
})
}
Usage-
CoroutineScope(Dispatchers.Main).launch {
getVideo(id)?.let {
//Do Something
} ?: kotlin.run{
//Video Not Found
}
}
you have to do something like this
#ExperimentalCoroutinesApi
suspend fun getVideo(id: String): Video? = coroutineScope {
val offlineVideo: Video? = suspendCancellableCoroutine { cont ->
offlineCatalog.findOfflineVideoById(id, object : OfflineCallback<Video> {
override fun onSuccess(video: Video?) {
cont.resume(video)
}
override fun onFailure(throwable: Throwable?) {
cont.resume(null)
}
})
}
offlineVideo ?: suspendCancellableCoroutine { cont ->
// offlineVideo not found so search from onlineCatalog
onlineCatalog.findVideoByID(id, object : VideoListener() {
override fun onVideo(video: Video?) {
cont.resume(video)
}
override fun onError(errors: MutableList<CatalogError>) {
super.onError(errors)
cont.resumeWithException(someException)
}
})
}
}
then you can call it as you wanted
someScope.launch {
try {
val video: Video? = getVideo(id)
//do something
} catch (throwable: Throwable) {
Log.e("Video not found")
}
}
Read more about suspendCancellableCoroutine here

IllegalStateException: Unable to retrieve AudioTrack pointer for write()

I am using a third-party text-to-speech tool to make my app ADA compliant. I am using this service to speak text every time text needs to be dictated:
class TTSService : Service() {
private val ttsUtils = TTSUtils.getInstance(this)
private val voiceText = ttsUtils.voiceText
private lateinit var audioTrack:AudioTrack
override fun onBind(intent: Intent?): IBinder? {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
audioTrack = AudioTrack(
AudioManager.USE_DEFAULT_STREAM_TYPE,
IVTDefine.SAMPLE_RATE_16000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
71296,
AudioTrack.MODE_STREAM
)
if (ttsUtils.isEnabled) {
ttsUtils.getEngineVoice()
if (null == voiceText) {
return START_STICKY
}
audioTrack.play()
try {
Thread(Runnable {
voiceText.vtapiTextToBuffer(intent?.getStringExtra("TextToSpeak"), //Library method that turns text string into audioTrack of
object : VoiceTextListener {
override fun onReadBufferWithWordInfo(output: ByteArray?, outputSize: Int, wordInfo: MutableList<SyncWordInfo>?) {
}
override fun onReadBuffer(output: ByteArray?, outputSize: Int) {
if (outputSize > 0) {
val audioData = ByteBuffer.wrap(output)
audioTrack.write(audioData, audioData.remaining(), AudioTrack.WRITE_BLOCKING)
}
}
override fun onReadBufferWithMarkInfo(output: ByteArray?, outputSize: Int, markInfo: MutableList<SyncMarkInfo>?) {
}
override fun onError(error: String?) {
Log.e(TAG, "Error: $error")
}
})
}).start()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
return START_REDELIVER_INTENT
}
override fun onDestroy() {
super.onDestroy()
voiceText.vtapiStopBuffer()
if (audioTrack.playState == AudioTrack.PLAYSTATE_PLAYING) {
try {
audioTrack.pause()
} catch (e: Exception) {
AppLog.e(TAG, e.message!!)
}
}
try {
audioTrack.flush()
audioTrack.release()
} catch (e: Exception) {
AppLog.e(TAG, e.message!!)
}
}
}
The service is always started with this method in another class:
fun speakText(textToSpeak: String) {
val i = Intent(mContext, TTSService::class.java)
i.putExtra("TextToSpeak", textToSpeak)
mContext.stopService(i)
mContext.startService(i)
}
I'm getting the above error when audioTrack.write() is called in the service's runnable, but I don't know what the error means. How do I make sure it's retrieving the pointer?

BoundaryCallback Not Called With DataSource

I created a boundary callback and a data source, the data source reads from the DB and the boundary callback is supposed to make the API calls and persist to the DB.
However, the overridden methods in boundary callback are never called, I don't know if there is a rule that says you can't mix both. But it is driving me crazy and I don't get it.
private val postsDataFactory = object: DataSource.Factory<String, Post>() {
override fun create(): DataSource<String, Post> {
return ItemDataSource(application)
}
}
private val postsBoundaryCallback = object: PagedList.BoundaryCallback<Post>(){
override fun onZeroItemsLoaded() {
super.onZeroItemsLoaded()
viewModelScope.launch {
try {
val posts = postsRepo.findPosts(mutableMapOf("limit" to "1"))
database.postDao().create(posts)
} catch (e: IOException) {
Log.d("NETWORK_ERROR", e.message)
} catch (e: Exception) {
Log.d("UNCAUGHT_ERROR", e.message)
}
}
}
override fun onItemAtEndLoaded(itemAtEnd: Post) {
viewModelScope.launch {
try {
val posts = postsRepo.findPosts(mutableMapOf(
"before" to itemAtEnd.id,
"limit" to "1"
))
database.postDao().create(posts)
} catch (e: IOException) {
Log.d("NETWORK_ERROR", e.message)
} catch (e: Exception) {
Log.d("UNCAUGHT_ERROR", e.message)
}
}
}
}
private val postPageConfig = PagedList
.Config
.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(10)
.build()
val postsLiveData = LivePagedListBuilder(
postsDataFactory,
postPageConfig
)
.setBoundaryCallback(postsBoundaryCallback)
.build()

Categories

Resources