How to access a dialog from workmanager's worker class - android-alertdialog

I have dialog in Activity and I need to access the dialog from worker class of workManager to show the dialog.
The dialog code is in Activity:
companion object {
fun showDialog(
context: Context,
title: String,
timeStamp: String,
) {
val dialog = Dialog(context)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(R.layout.alert_layout)
val closeImage = dialog.findViewById(R.id.iv_close) as ImageView
val tvTimeStamp = dialog.findViewById(R.id.tv_timeStamp) as TextView
val llAlertMain = dialog.findViewById(R.id.ll_alert_main) as LinearLayout
tvTimeStamp.text = timeStamp
closeImage.setOnClickListener {
dialogIsShowing = false
dialog.dismiss()
}
dialog.show()
dialogIsShowing = true
}
}
The worker class code:
override fun doWork(): Result {
try {
CoroutineScope(Dispatchers.IO).launch{
showDialog()
}
return Result.success()
}catch (e : Exception){
return Result.failure()
}
}
private showDialog(){
try {
MainActivity.showDialog(applicationContext, title, timeStamp)
//CustomDialogFragment.newInstance("Title", "SubTitle").show(supportFragmentManager, CustomNotificationDialogFragment.TAG)
}catch (e: Exception){
e.printStackTrace()
}
}
but it is giving error : Unable to add window -- token null is not valid; is your activity running?

You need to have a design:
MVVM for example.
The Activity is the View. It will observe via LiveData updates from the ViewModel. The event will be: "show the dialog".
Then you create some shared object. Observable pattern. You send updates to this shared object from the Worker.
You observe the shared object via the ViewModel and from there you update the LiveData.

Related

What is the best way to get data from an API using retrofit when something needs to wait on that data?

I have a retrofit API call but am having trouble getting the data out of it:
This is in a class file that's not a viewModel or Fragment. It's called from the apps main activity view model. I need to be able to get the data from the API and wait for some processing to be done on it before returning the value back the view model. Newer to kotlin and struggling with all the watchers and async functions. The result of this an empty string is the app crashes, because it's trying to access data before it has a value.
From class getData which is not a fragment
private lateinit var data: Data
fun sync(refresh: Boolean = false): List<String> {
var info = emptyList<String>
try {
getData(::processData, ::onFailure)
info = data.info
} catch(e: Throwable){
throw Exception("failed to get data")
}
}
}
return info
}
fun getData(
onSuccess: KFunction1<ApiResponse>?, Unit>,
onFailed: KFunction1<Throwable, Unit>
) {
val client = ApiClient().create(Service.RequestData)
val request = client.getData()
request.enqueue(object : Callback<ApiResponse> {
override fun onResponse(
call: Call<ApiResponse>,
response: Response<ApiResponse>
) {
onSuccess(response.body())
}
override fun onFailure(call: Call<RegistryResponse<GlobalLanguagePack>>, t: Throwable) {
onFailed(Exception("failed to get data"))
}
})
}
private fun processData(body: ApiResponse?) {
requireNotNull(body)
data = body.data
}
```
From appViewModel.kt:
```
fun setUpStuff(context: Context, resources: AppResources) = viewModelScope.launch {
val stuff = try {
getData.sync()
} catch (e: Exception) {
return#launch
}
if (stuff.isEmpty()) return#launch
}
```

how to test project reactor code that does not return a subscription

i'm trying to create a component that stream data from remote service continuously. The component starts and stops according to spring container lifecycle. I'm not sure how to test this component as the subscription is done inside my component so i was wondering wether this is the correct way to implement this kind of component with webflux or not. Does anybody know any similar component in any framework from where i might take some ideas?
Regards
class StreamingTaskAdapter(
private val streamEventsUseCase: StreamEventsUseCase,
private val subscriptionProperties: subscriptionProperties,
) : SmartLifecycle, DisposableBean, BeanNameAware {
private lateinit var disposable: Disposable
private var running: Boolean = false
private var beanName: String = "StreamingTaskAdapter"
private val logger = KotlinLogging.logger {}
override fun start() {
logger.info { "Starting container with name $beanName" }
running = true
doStart()
}
private fun doStart() {
disposable = Mono.just(
CreateSubscriptionCommand(
subscriptionProperties.events,
subscriptionProperties.owningApplication
)
)
.flatMap(streamEventsUseCase::createSubscription)
.flatMap { subscription ->
Mono.just(subscription)
.map(::ConsumeSubscriptionCommand)
.flatMap(streamEventsUseCase::consumeSubscription)
}
.repeat()
.retryWhen(Retry.backoff(MAX_ATTEMPTS, Duration.ofSeconds(2)).jitter(0.75))
.doOnSubscribe { logger.info { "Started event streaming" } }
.doOnTerminate { logger.info { "Stopped event streaming" } }
.subscribe()
}
override fun stop() {
logger.info("Stopping container with name $beanName")
doStop()
}
override fun isRunning(): Boolean = running
private fun doStop() {
running = false
disposable.dispose()
}
override fun destroy() {
logger.info("Destroying container with name $beanName")
doStop()
}
override fun setBeanName(name: String) {
this.beanName = name
}
companion object {
const val MAX_ATTEMPTS: Long = 3
}
}

How to handle Kotlin Jetpack Paging 3 exceptions?

I am new to kotlin and jetpack, I am requested to handle errors (exceptions) coming from the PagingData, I am not allowed to use Flow, I am only allowed to use LiveData.
This is the Repository:
class GitRepoRepository(private val service: GitRepoApi) {
fun getListData(): LiveData<PagingData<GitRepo>> {
return Pager(
// Configuring how data is loaded by adding additional properties to PagingConfig
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = {
// Here we are calling the load function of the paging source which is returning a LoadResult
GitRepoPagingSource(service)
}
).liveData
}
}
This is the ViewModel:
class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {
private val _gitReposList = MutableLiveData<PagingData<GitRepo>>()
suspend fun getAllGitRepos(): LiveData<PagingData<GitRepo>> {
val response = repository.getListData().cachedIn(viewModelScope)
_gitReposList.value = response.value
return response
}
}
In the Activity I am doing:
lifecycleScope.launch {
gitRepoViewModel.getAllGitRepos().observe(this#PagingActivity, {
recyclerViewAdapter.submitData(lifecycle, it)
})
}
And this is the Resource class which I created to handle exceptions (please provide me a better one if there is)
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
As you can see I am using Coroutines and LiveData. I want to be able to return the exception when it occurs from the Repository or the ViewModel to the Activity in order to display the exception or a message based on the exception in a TextView.
Your GitRepoPagingSource should catch retryable errors and pass them forward to Paging as a LoadResult.Error(exception).
class GitRepoPagingSource(..): PagingSource<..>() {
...
override suspend fun load(..): ... {
try {
... // Logic to load data
} catch (retryableError: IOException) {
return LoadResult.Error(retryableError)
}
}
}
This gets exposed to the presenter-side of Paging as LoadState, which can be reacted to via LoadStateAdapter, .addLoadStateListener, etc as well as .retry. All of the presenter APIs from Paging expose these methods, such as PagingDataAdapter: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter
You gotta pass your error handler to the PagingSource
class MyPagingSource(
private val api: MyApi,
private val onError: (Throwable) -> Unit,
): PagingSource<Int, MyModel>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, YourModel> {
try {
...
} catch(e: Exception) {
onError(e) // <-- pass your error listener here
}
}
}

AlertDialog Listener not attached

MyAlertDialog throws ClassCastException when trying to set the context to the listener. I'm calling the MyAlertDailog from a fragment.
I'm using the guide found in the android dev docs.
https://developer.android.com/guide/topics/ui/dialogs#PassingEvents
MyFragment
class MyFragment : Fragment(), MyAlerDialog.MyAlertDialogListener {
...
fun launchAlertDialog() {
val dailog = MyAlertDialog().also {
it.show(requireActivity().supportFragmentManager, "DialogInfoFragment")
}
}
override fun onDialogPostiveCLick(dialog: DialogFragment) {
Log.i(TAG, "Listener returns a postive click")
}
}
MyAlertDialog
class MyAlertDialog : DialogFragment() {
// Use thsi instance for the interface
internal var listener: MyAlertDialogListener
// My Fragment implements this interface.
interface MyAlertDialogListener {
onDialogPositiveClick(dialog: DialogFragment)
}
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host implements the callback.
try {
// My problem is here.
listener = context as MyAlertDailog
} catch (e: ClassCastException) {
// exception thrown here.
throw ClassCastException((context.toString() + " must implement MyAlertDailogListener")
}
}
override fun onCreateDialog(savedInstanceState:Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.builder(it)
...
builder.setPosiviveButton("Positive button",
DialogInterface.OnClickListener {
listener.onDialogPositiveClick(this)
}
}
}
}
Error report
java.lang.ClassCastException: com.example.androiddevpractice.MainActivity#ab56136 must implement MyAlertDialogListener
at com.example.androiddevpractice.topics.userinterface.dialog.MyAlertDialog.onAttach(MyAlertDialog.kt:35)
Even though the launchAlertDialog() method is inside of MyFragment, the "host" for MyAlertDialog is your Activity, not MyFragment.
Implement MyAlerDialog.MyAlertDialogListener inside of MainActivity in order for the cast to succeed. MainActivity can then communicate to MyFragment if it has to.
Alternatively, you could use setTargetFragment() in order to "connect" MyFragment and MyAlertDialog directly:
val dialog = MyAlertDialog()
dialog.setTargetFragment(this, 0)
dialog.show(requireActivity().supportFragmentManager, "DialogInfoFragment")
Then, rather than overriding onAttach() and casting a context, you would cast the results of getTargetFragment():
builder.setPosiviveButton(
"Positive button",
DialogInterface.OnClickListener {
val listener = targetFragment as MyAlertDialogListener
listener.onDialogPositiveClick(this)
}
)

Navigation controller AlertDialog click listner

I'm using Android's Navigation component and I'm wondering how to setup AlertDialog from a fragment with a click listener.
MyFragment
fun MyFragment : Fragment(), MyAlertDailog.MyAlertDialogListener {
...
override fun onDialogPostiveCLick(dialog: DialogFragment) {
Log.i(TAG, "Listener returns a postive click")
}
fun launchMyAlertDialog() {
// Here I would typically call setTargetFragment() and then show the dialog.
// but findnavcontroller doesn't have setTargetFragment()
findNavController.navigate(MyFragmentDirection.actionMyFragmentToMyAlertDialog())
}
}
MyAlertDialog
class MyAlertDialog : DialogFragment() {
...
internal lateinit var listener: MyAlertDialogListener
interface MyAlertDialogListener{
fun onDialogPostiveCLick(dialog: DialogFragment)
}
override fun onCreateDialog(savdInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage("My Dialog message")
.setPositiveButton("Positive", DialogInterface.OnClickListener {
listener = targetFragment as MyAlertDialogListener
listener.onDialogPositiveClick(this)
}
...
}
}
}
This currently receives a null point exception when initializing the listener in MyAlertDialog.
To use targetFragment, you have to set it first as you commented, unfortunately jetpack navigation does not do this for you (hence the null pointer exception). Check out this thread for alternative solution: https://stackoverflow.com/a/50752558/12321475
What I can offer you is an alternative. If the use-case is as simple as displaying a dialog above current fragment, then do:
import androidx.appcompat.app.AlertDialog
...
class MyFragment : Fragment() {
...
fun onDialogPostiveCLick() {
Log.i(TAG, "Listener returns a postive click")
}
fun launchMyAlertDialog() {
AlertDialog.Builder(activity)
.setMessage("My Dialog message")
.setPositiveButton("Positive") { _, _ -> onDialogPostiveCLick() }
.setCancellable(false)
.create().show()
}
}