I have an application in ionic and I need to customize the redirection with the Android back button. But on several occasions the function I have is not executed correctly or it executes other page routing functions or previous components. Also sometimes it is executed two or more times.
I have tried with unsubscribe in ionViewWillEnter, DidEnter, ngOnDestroy
If I use this function in contructor method, this action replies in all pages
constructor(
private _router: Router,
private _navController: NavController,
private _userService: UserService,
private _alertService: AlertService,
private translate: TranslateService,
private platform: Platform
) {}
ngOnInit() {
this.getCashInfo();
// this.stockId = url[url.length - 1];
}
ionViewDidEnter() {
this.subscriptionBack = this.platform.backButton.subscribe(() => {
this.goBack();
this._router.navigate(['tabs/tabs/trade']);
});
}
ionViewDidLeave() {
this.subscriptionBack.unsubscribe();
}
Related
How do you trigger a native Android back button press from inside a React Native button that is handled somewhere else in the application.
For example,
<Pressable onPress={nativeGoBack} />
The event is then handled in another component using the BackHandler API.
BackHandler.addEventListener('hardwareBackPress', () => {
// Perform action
});
NB: This is not a React Navigation specific question (So navigation.goBack() is not a solution).
If you want to communicate from react native to adnroid, you should use Native modules.
See documentation https://reactnative.dev/docs/native-modules-android
In short:
Create a native module which handle backbutton from specific activity
class BackPressReactModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "BackPressReactModule"
}
#ReactMethod
fun goBack() {
val context = reactApplicationContext
val currentActivity = context.currentActivity as Activity
currentActivity.onBackPressed()
}
}
Register BackPressReactModule into your packages in ReactNativeHost. (See documentation)
After successfully exposing module, you can call it from javascript like this:
import {NativeModules} from 'react-native';
const {BackPressReactModule} = NativeModules;
BackPressReactModule.goBack();
I'm working on a pet project where I'm trying to create a hybrid app using a WebView. The web platform that I run in the WebView sends events to the WebView/App through a #JavascriptInterface object. I can also command the web navigation by running a set of javascript functions against the web platform via the WebView using the evaluateJavascript(String, (String) -> Unit) function.
What I'm trying to achieve right now is that these commands that I execute through the evaluateJavascript(String, (String) -> Unit) function run sequentially. I might execute these commands from many different places at the same time, so I want them to run, wait for the callback from the evaluateJavascript() function to get called, and then execute the next command in the queue.
This is what I have in my custom WebView class:
val scriptQueue = mutableListOf<String>()
fun queueEvaluateJavascript(script: String) {
if (webViewIsLoading) {
scriptQueue.add(script)
} else {
scriptQueue.add(script)
runScriptQueue()
}
}
fun runScriptQueue() {
for (script in scriptQueue) {
evaluateJavascript(script, { })
}
scriptQueue.clear()
}
As you can see this is a super basic approach, and I don't really account for the evaluateJavascript() callback. Ideally, I'd like to find a way to flat map each of this evaluateJavascript() calls so we execute one after another, but waiting for the callback to go through.
With RxJava I think I'd create an Observable and then have the evaluateJavascript() callback trigger the subscriber's onNext(). Since, I'm using Kotlin Coroutines I wanted to do something with Coroutines, so I can queue these evaulateJavascript() calls. But I'm not 100% sure what would be the equivalent here.
That would be a nice problem to approach with coroutines.
The usual way to convert callback based APIs to suspend functions is the following:
suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
evaluateJavascript(script) { result ->
cont.resume(result)
}
}
You can then use that in combination maybe with a Channel (to serve as a queue) and a coroutine that processes this channel:
class MyWebView(context: Context) : WebView(context) {
private val jsQueue = Channel<String>(BUFFERED)
fun startJsProcessingLoopIn(scope: CoroutineScope) {
scope.launch {
for (script in jsQueue) {
evaluateJs(script)
}
}
}
// you could also make this function non-suspend if necessary by calling
// sendBlocking (or trySend depending on coroutines version)
suspend fun queueEvaluateJavascript(script: String) {
jsQueue.send(script)
}
private suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
evaluateJavascript(script) { result ->
cont.resume(result)
}
}
}
Alternatively you can create your own coroutine scope and make sure to tie it with some sort of lifecycle of your webview (I'm not familiar with WebView so I'll let you judge which kind of method is correct):
class MyWebView2(context: Context) : WebView(context) {
// you can even further customize the exact thread pool used here
// by providing a particular dispatcher
private val jsProcessingScope = CoroutineScope(CoroutineName("js-processing"))
private val jsQueue = Channel<String>(BUFFERED)
// this starts the loop right away but you can also put this in a method
// to start it at a more appropriate moment
init {
jsProcessingScope.launch {
for (script in jsQueue) {
evaluateJs(script)
}
}
}
// you could also make this function non-suspend if necessary by calling
// sendBlocking (or trySend depending on coroutines version)
suspend fun queueEvaluateJavascript(script: String) {
jsQueue.send(script)
}
private suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
evaluateJavascript(script) { result ->
cont.resume(result)
}
}
fun someCloseOrDisposeCallback() {
jsProcessingScope.cancel()
}
}
I want to implement two view tab, with recycle view as list, and using paging 3 for collecting data and return will be kotlin flow. everthing is working perfectly at tab one, but nothing show in tab two.
the viewModel code (SharedViewModel):
#ExperimentalPagingApi
#HiltViewModel
class MovieViewModel #Inject constructor(
private val repository: MovieRepository,
private val dispatcher: CoroutineDispatcher
) : ViewModel() {
private val TAG = "MovieVM"
private var _moviePaging: Flow<PagingData<MovieEntities>>? =
repository.getMovie().cachedIn(viewModelScope)
private var _tvPaging: Flow<PagingData<MovieEntities>>? =
repository.getTv().cachedIn(viewModelScope)
init {
loadMovie()
}
// new paging
var moviePaging = MutableStateFlow<PagingData<MovieEntities>>(PagingData.empty())
var tvPaging = MutableStateFlow<PagingData<MovieEntities>>(PagingData.empty())
private fun loadMovie() {
viewModelScope.launch(dispatcher) {
_moviePaging?.collectLatest {
moviePaging.value = it
}
_tvPaging?.collectLatest {
tvPaging.value = it
}
}
}
the code is run. when i am debuging, only _moviePaging is call and show logger retrofit GET, but the _tvPaging is nothing, not call api, like never triggered to run (unreachable?)
so, i was change order, call _tvPaging first. then only _tvPaging is run.
I want two line of code _moviePaging and _tvPaging running, but now is just one of them. Please Help.
Any response will apreciate.
after reading some documentation, and understand about work flow coroutine,
one coroutine scope for hot flow only one methode can run, they suspend forever
so, the solution is make viewModelScope for each hot flow
viewModelScope.launch(dispatcher) {
_moviePaging?.collectLatest {
moviePaging.value = it
}
}
viewModelScope.launch(dispatcher) {
_tvPaging?.collectLatest {
tvPaging.value = it
}
}
I'm running UI testing on Android devices using Appium. We recently migrated to JUnit5 and I'm attempting to utilize the #BeforeAll class to make sure the app is in a good state before we continue to the next class.
Currently, the tooltip in Android studio is indicating that the function is never used. In the log I'm seeing a junitException saying that the method must be static. I haven't implemented #TestInstance yet, I'd like to be able to use beforeAll without it for now. I'm just confused why it isn't working since my #beforeEach and #afterEach are both working. The error and code are below.
org.junit.platform.commons.JUnitException: #BeforeAll method 'public final void com.bypass.automation.BaseTest.healthcheck()' must be static unless the test class is annotated with #TestInstance(Lifecycle.PER_CLASS).
open class BaseTest {
lateinit var driver: AndroidDriver<MobileElement>
private val capabilities = DesiredCapabilities().apply {
setCapability(APPIUM_VERSION, "1.19.1")
setCapability(PLATFORM_NAME, "Android")
setCapability(DEVICE_NAME, "Android")
setCapability("appPackage", "com.ourpackage")
setCapability("appActivity", "com.ourpackage.PassthroughHomeActivity")
setCapability("automationName", "uiautomator2")
setCapability("skipDeviceInitialization", true)
setCapability("noReset", true)
setCapability("full-reset", false)
setCapability("enableMultiWindows", false)
setCapability("unlockType", "pin")
setCapability("unlockKey", "0000")
setCapability("newCommandTimeout", "120")
}
#BeforeAll
fun healthcheck() {
val currentActivity = driver.currentActivity()
println("Current activity is $currentActivity")
if (currentActivity.contains("StationSecurePayActivity")) {
println("Exiting Station Pay")
CreditCardEntryView(driver).clickBackButton()
}
when {
currentActivity.contains("kiosk") -> {
Thread.sleep(2000)
println("Exiting Kiosk")
KioskView(driver).exitKiosk()
println("Logging out")
LogInProviderUtil(driver).logOut()
}
currentActivity != ".LoginActivity" -> {
println("Logging out")
LogInProviderUtil(driver).logOut()
}
currentActivity.contains(".LoginActivity") -> {
println("Session was properly logged out. No action taken.")
}
}
}
#BeforeEach
fun setup() {
driver = AndroidDriver(URL("http://127.0.0.1:4750/wd/hub"), capabilities)
driver.manage()?.timeouts()?.implicitlyWait(30, SECONDS)
if (LogInProviderUtil(driver).isLoggedIn()){
LogInProviderUtil(driver).logOut()
}
}
#AfterEach
fun teardown() {
if (LogInProviderUtil(driver).isLoggedIn()){
LogInProviderUtil(driver).logOut()
driver.quit()
}
else {
driver.quit()
}
}
}
It will work. I believe that any method annotated with #BeforeAll must be static (unless the "per-class" test instance lifecycle is used). So it sounds to me like you should switch to that by adding this annotation to your test class: #TestInstance(Lifecycle.PER_CLASS)
Also, it is usual practice to make your setup and teardown methods public. Also, I recommend use of Selenium-Jupiter framework (https://github.com/bonigarcia/selenium-jupiter/blob/master/README.md#appium) . Good luck.
If you want to have an initialization block you may put it simply into
init{} method. And you don't have to annotate it.
I'm working on my first MvvmCross project and I'm using a Splash Screen to get some user data from sqlite database and start Login or Main View Model depending on there is or no data.
Then I would like to send those data to the MainViewModel. I would like to know if that if possible to make and how to do it.
This is my MvxSplashScreenActivity code:
[Activity(Theme = "#style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashScreen : MvxSplashScreenActivity
{
protected override void OnResume()
{
base.OnResume();
Task startupWork = new Task(() => { SimulateStartup(); });
startupWork.Start();
}
async void SimulateStartup()
{
Task<Core.Models.TrackrUserData> result = Mvx.Resolve<RepositoryService>().GetUserDataAsync();
await Task.Delay(2000);
Core.Models.TrackrUserData userData = result.Result;
if(userData != null){
await GetProjects(userData);
await Task.Delay(1000);
}else{
await Task.Delay(1000);
}
}
async Task<bool> GetProjects(Core.Models.TrackrUserData login)
{
var apiService = new ApiService();
var respuesta = await apiService.GetProjects(login.IdUser.ToString(), login.ActiveTeamId.ToString(), login.Jwt);
await Mvx.Resolve<RepositoryService>().DeleteProjects();
var projectsSaved = SaveProjects(respuesta);
return true;
}
async Task<bool> SaveProjects(List<ProjectGetResponse> respuesta)
{
foreach (var item in respuesta)
{
await Mvx.Resolve<RepositoryService>().CreateProject(new Project
{
Id = item.Id,
Name = item.Name
});
}
return true;
}
}
Thanks in advance!
I wouldn't recommend you to perform all the async work at a View level. Business logic and heavy work is not a concern of the View. Think about this: If you were to create an iOS version of the App, you will need to replicate all the code on that platform too...
You can take advantage of the MVVM implementation that MvvmCross offers and do all the operations at a Core level.
In order to do this, you can follow these steps:
1) Create a class that derives from MvxNavigationServiceAppStart.
2) Override Start method and make sure you navigate to a ViewModel (MainViewModel in your case) before the method ends.
3) Before navigating, you can perform all the operations that you want. But if you were to do async/await you will end up with an async void Start method... which isn't really good. So my recommendation here is to store a simple value to make the initial decisions using something like this plugin and load fast.
4) After that you can do all the heavy work on your initial ViewModel (maybe MainViewModel?).
Final hint: You should consider loading all dependencies using Dependency Injection in constructors instead of resolving them through the IoC Container.