Mixed Jetpack Compose / Activity app navigation: from compose to activity and back - android-activity

I am a newbie in Android development so I might be missing something simple here.
I have a compose setup using androidx.navigation:navigation-compose:2.5.3 where I have the routing setup:
sealed class Routes(val route: String) {
object Login : Routes("Login")
object TermsOfService : Routes("TermsOfService")
object Home : Routes("Home")
}
#Composable
fun Routing(startDestination: String) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = startDestination) {
composable(Routes.Login.route) {
LoginPage(navController = navController, LoginViewModel(AppEnvironment.sdk))
}
composable(Routes.TermsOfService.route) {
TermsOfService(navController = navController)
}
composable(Routes.Home.route) {
Home(navController = navController)
}
}
}
I would setup my MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App_SenderTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val isLoggedIn = AppEnvironment.encryptedSettingsRepo.isUserLoggedIn.get().toBoolean()
println("Routing based on is_user_logged_in $isLoggedIn")
if (isLoggedIn) {
Routing(startDestination = Routes.Home.route)
} else {
Routing(startDestination = Routes.Login.route)
}
}
}
}
}
}
The Home itself is an Activity setup like:
#Composable
fun Home(navController: NavHostController) {
Box(modifier = Modifier.fillMaxSize()) {
ScaffoldWithTopBarHome(navController)
}
}
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#Composable
fun ScaffoldWithTopBarHome(navController: NavHostController) {
Scaffold(
topBar = {
CustomTopAppBar(navController, "Sender", true)
}, content = {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val context = LocalContext.current
context.startActivity(Intent(context, HomeActivity::class.java))
}
})
}
class HomeActivity : AppCompatActivity() {
fun logout() {
// HOW TO GO BACK TO COMPOSE IF I DON'T HAVE A COMPOSE NAVIGATION REFERENCE
}
}
I can easily navigate to the HomeActivity from any composable setup since I have the NavHostController, e.g. navController.navigate(Routes.Home.route), but how can I navigate back from the "activityworld` back to compose land.
Thanks.

Related

How to set darkmode in webview using jetpack compose?

I try to enable dark mode on webview, which is not working, and also setForceDark is deprecated.
I am looking solution to enable dark mode on web view using jetpack compose
package com.blogspot.boltuix
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.os.Bundle
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebViewPage("https://www.boltuix.com/")
}
}
}
#SuppressLint("SetJavaScriptEnabled")
#Composable
fun WebViewPage(url: String){
val context = LocalContext.current
//The Configuration object represents all of the current configurations, not just the ones that have changed.
val configuration = LocalConfiguration.current
when (configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
Toast.makeText(context, "landscape", Toast.LENGTH_SHORT).show()
}
else -> {
Toast.makeText(context, "portrait", Toast.LENGTH_SHORT).show()
}
}
// Adding a WebView inside AndroidView
// with layout as full screen
AndroidView(factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
// to play video on a web view
settings.javaScriptEnabled = true
// to verify that the client requesting your web page is actually your Android app.
settings.userAgentString = System.getProperty("http.agent") //Dalvik/2.1.0 (Linux; U; Android 11; M2012K11I Build/RKQ1.201112.002)
// feature 1 : dark mode (auto system setup)
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_ON)
}
loadUrl(url)
}
}, update = {
it.loadUrl(url)
})
}
App-level Gradle file:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.blogspot.boltuix'
compileSdk 33
defaultConfig {
applicationId "com.blogspot.boltuix"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.2.0-beta01'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
implementation 'androidx.activity:activity-compose:1.5.0'
implementation "androidx.compose.ui:ui:1.3.0-alpha01"
implementation "androidx.compose.ui:ui-tooling-preview:1.3.0-alpha01"
implementation 'androidx.compose.material3:material3:1.0.0-alpha14'
testImplementation 'junit:junit:4.13.2'
implementation "androidx.webkit:webkit:1.5.0-beta01"
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.3.0-alpha01"
debugImplementation "androidx.compose.ui:ui-tooling:1.3.0-alpha01"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.3.0-alpha01"
}
I tested OS 11 and 13 - WEB VIEW is working, but not changing dark mode.
This is what I did...
Added this dependency in build.gradle.
implementation "androidx.webkit:webkit:$webkitVersion"
Declare the object below. It is responsible to provide the correct Configuration object in according to the Dark Mode.
object ConfigurationUtil {
fun getConfiguration(context: Context): Configuration {
/**
* issue tracker : https://issuetracker.google.com/issues/170328697
* Web view resets uiMode (Day/Night) in Configuration
*/
val configuration = context.resources.configuration
val configurationNighMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
val appCompatNightMode = AppCompatDelegate.getDefaultNightMode()
val newUiModeConfiguration = when {
configurationNighMode == Configuration.UI_MODE_NIGHT_NO && appCompatNightMode == UiModeManager.MODE_NIGHT_YES -> {
Configuration.UI_MODE_NIGHT_YES or (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
configurationNighMode == Configuration.UI_MODE_NIGHT_YES && appCompatNightMode == UiModeManager.MODE_NIGHT_NO -> {
Configuration.UI_MODE_NIGHT_NO or (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK.inv())
}
else -> null
}
if (newUiModeConfiguration != null) {
val fixedConfiguration = Configuration().apply {
configuration.uiMode = newUiModeConfiguration
}
context.createConfigurationContext(fixedConfiguration)
}
return configuration
}
}
Use the WebView inside of an AndroidView....
#Composable
fun WebViewScreen() {
AndroidView(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
factory = { context ->
object : WebView(context) {
init {
val assetLoader = WebViewAssetLoader.Builder()
.addPathHandler("/res/", WebViewAssetLoader.ResourcesPathHandler(context))
.build()
webChromeClient = WebChromeClient()
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
return false
}
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return assetLoader.shouldInterceptRequest(request.url)
}
}
setBackgroundColor(0)
val configuration = ConfigurationUtil.getConfiguration(context)
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
when (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> {
WebSettingsCompat.setForceDark(
settings,
WebSettingsCompat.FORCE_DARK_ON
)
}
Configuration.UI_MODE_NIGHT_NO, Configuration.UI_MODE_NIGHT_UNDEFINED -> {
WebSettingsCompat.setForceDark(
settings,
WebSettingsCompat.FORCE_DARK_OFF
)
}
else -> {
WebSettingsCompat.setForceDark(
settings,
WebSettingsCompat.FORCE_DARK_AUTO
)
}
}
}
settings.allowFileAccess = false
settings.allowContentAccess = false
setDownloadListener { url, _, _, _, _ ->
url?.let {
try {
context.startActivity(
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(it)
}
)
} catch (e: Exception) {
Toast.makeText(context, "Error opening link", Toast.LENGTH_LONG)
.show()
}
}
}
loadDataWithBaseURL(
ResourceBaseUrl,
"This is a web view. Check if it is working on Dark Mode",
"text/html",
"UTF-8",
null
)
}
}
}
)
}

Tabbar is not hided on subpages in Ionic 5

In ionic 4 or 5, tabbar is not hided on subpages.
Of course, it works well in ionic 2 or 3.
Please let me know how to solve this issue.
This is my solution.
But hope the best solution.
create TabsService
import this in app.module.ts
Here is full code of TabsService
import { Injectable } from '#angular/core';
import { filter } from 'rxjs/operators';
import { NavigationEnd, Router } from '#angular/router';
import { Platform } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class TabsService {
constructor(private router: Router, private platform: Platform) {
this.platform.ready().then(() => {
this.navEvents();
});
}
public hideTabs() {
const tabBar = document.getElementById('kidesiaTabBar');
if (tabBar && tabBar.style.display !== 'none') {
tabBar.style.display = 'none';
}
}
public showTabs() {
const tabBar = document.getElementById('kidesiaTabBar');
if (tabBar && tabBar.style.display !== 'flex') {
tabBar.style.display = 'flex';
}
}
private navEvents() {
this.router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe((e: any) => {
this.showHideTabs(e);
});
}
private showHideTabs(e: any) {
const urlArray = e.url.split('/');
if (urlArray.length >= 3) {
let shouldHide = true;
if (urlArray.length === 3 && urlArray[1] === 'tabs') {
shouldHide = false;
}
try {
setTimeout(() => (shouldHide ? this.hideTabs() : this.showTabs()), 300);
} catch (err) {}
}
}
}

IONIC-3 NavController throwing can't resolve all parameters error

I have an interesting problem with IONIC-3 that I've not been able to solve. I am attempting to implement an auth routing which is triggered by ionViewCanEnter. However, while I can pass one nav setter, it will not allow multiple. Here is the code:
AuthService Function:
isAuthenticated(nav: NavController): boolean | Promise<any> {
const userAuth = this.uData.getAuthenticated;
const userProfile = this.uData.getUserProfile;
if (userAuth ) {
//User is logged in, so let's check a few things.
if (!userProfile.sign_up_complete) {
//User has not completed sign up
setTimeout(() => { nav.setRoot(CreateAccountPage) }, 0);
}
return true
} else {
//User is not authenticated, return to walkthrough
setTimeout(() => { nav.setRoot(WalkthroughPage) }, 0);
return false
}}
Example calling:
ionViewCanEnter(): boolean | Promise<any> {
return this.auth.isAuthenticated(this.nav);
}
If I have only CreateAccountPage, the script runs fine. However, when I add WalkthroughPage, it throws the following error:
Error: Can't resolve all parameters for ListingPage: (?, [object Object], [object Object], [object Object]).
Which is an error related to the AuthService. For clarity the WalkthroughPage code is as follows:
import { Component, ViewChild } from '#angular/core';
import { IonicPage, NavController, Slides } from 'ionic-angular';
import { RemoteConfigProvider } from '../../providers/remote-config/remote-config';
import { LoginPage } from '../login/login';
import { SignupPage } from '../signup/signup';
#IonicPage()
#Component({
selector: 'walkthrough-page',
templateUrl: 'walkthrough.html'
})
export class WalkthroughPage {
lastSlide = false;
sign_up_enabled: null;
sign_in_enabled: null;
#ViewChild('slider') slider: Slides;
constructor(public nav: NavController,
public remoteConfig: RemoteConfigProvider) {
}
ionViewDidLoad() {
this.remoteConfig.getValue('sign_up_enabled').then(t => {
this.sign_up_enabled = t;
})
this.remoteConfig.getValue('sign_in_enabled').then(t => {
this.sign_in_enabled = t;
})
}
skipIntro() {
this.lastSlide = true;
this.slider.slideTo(this.slider.length());
}
onSlideChanged() {
this.lastSlide = this.slider.isEnd();
}
goToLogin() {
this.nav.push(LoginPage);
}
goToSignup() {
this.nav.push(SignupPage);
}
}
I have attempted to compare both pages, but not identified the exact cause. I welcome any thoughts.
For those who encounter a similar issue, the fix was straight forward. I simply used deep-linking reference which resolved all issues. Example below.
isAuthenticated(nav: NavController): boolean | Promise<any> {
const userAuth = this.userStore.getAuthenticated;
const userProfile = this.userStore.getUserProfile;
if (userAuth) {
return true
} else {
console.log('Auth guard: Not authenticated');
setTimeout(() => { nav.setRoot('no-access') }, 0);
return false
}
}

Ionic Searchbar with PHP API

It works but i anot getting the results it should sort. I am getting the same results regardless what i type in the searchbar
I want it to sort like autocomplete. to show results of what i type in the search bar
search.ts
#Component({ selector: "page-search", templateUrl: "search.html" })
export class SearchPage {
filter: string = '';
public userDetails: any;
public resposeData: any;
public dataSet: any;
public userSet: any;
public mediaSet: any;
public noRecords: boolean;
userPostData = {
uid: "",
token: "",
username: "",
bio: ""
};
constructor(
public common: Common,
public navCtrl: NavController,
public app: App,
public menu: MenuController,
public authService: AuthService,
public http: Http,
platform: Platform,
statusBar: StatusBar,
splashScreen: SplashScreen
) {
this.initializeItems();
this.mostmediaList();
}
initializeItems() {
return this.userPostData;
}
getItems(ev: any) {
this.initializeItems();
let val = ev.target.value;
if (val && val.trim() != '') {
this.authService.postData(this.userPostData, "userGroupSearch").then(
result => {
this.resposeData = result;
if (this.resposeData.allArtistsData) {
this.userSet = this.resposeData.allArtistsData;
console.log(this.userSet);
} else {
console.log("No access");
}
},
);
}
}
Since your code is wrapped into
if (this.resposeData.items) {
//some code
}
we know for sure that this.resposeData is not an array, since it has an items member (otherwise your code inside the if would not be executed and hence you would not get an error as in the case we have).
Since you call the parameter items at
this.userSet = this.resposeData.filter((items) => {
//some code
};
it is safe to assume that you wanted to filter this.resposeData.items instead of this.resposeData. So, you will need to make sure it is an array at the if
if (this.resposeData.items && Array.isArray(this.resposeData.items)) {
//some code
}
and filter this.resposeData.items instead of this.resposeData:
this.userSet = this.resposeData.items.filter((items) => {
//some code
};

How to get rid of this hack

I want to modify the class so that it does not use the ApplicationRef. In other words how to get hold of main app not using app ref.
#Injectable()
export class ToastsManager {
container: ComponentRef<any>;
private options = {
autoDismiss: true,
toastLife: 1000
};
private index = 0;
container: ComponentRef<any>;
private options = {
autoDismiss: true,
toastLife: 1000
};
private index = 0;
constructor(private resolver: ComponentResolver,
private appRef: ApplicationRef,
#Optional() #Inject(ToastOptions) options) {
if (options) {
Object.assign(this.options, options);
}
}
show(toast: Toast) {
if (!this.container) {
// a hack to get app element in shadow dom
let appElement: ViewContainerRef = new ViewContainerRef_(this.appRef['_rootComponents'][0]._hostElement);
this.resolver.resolveComponent(ToastContainer)
.then((factory: ComponentFactory<any>) => {
this.container = appElement.createComponent(factory);
this.setupToast(toast);
});
} else {
this.setupToast(toast);
}
}
I try with the #ViewChild but it does not work.
You could do with ApplicationRef what Brandon Roberts demonstrates in https://github.com/angular/angular/issues/4112#issuecomment-139381970 to get a reference to the Router in CanActivate().
Probably better would be a shared service
#Injectable()
export class Shared {
appRef = new BehaviorSubject();
setAppRef(appRef:ApplicationRef) {
this.appRef.emit(appRef);
}
}
export class ToastsManager {
constructor(private resolver: ComponentResolver,
private appRef: ApplicationRef,
shared:Shared,
#Optional() #Inject(ToastOptions) options) {
shared.setAppRef(appRef);
}
}
export class OtherClassThatNeedsAppRef {
constructor(shared:Shared) {
shared.appRef.subscribe(appRef => this.appRef = appRef);
}
}