How do I make a screen update when a boolean becomes true? - java

if(isPlayerNearby) {
Text("Player $playerName is within range!")
Image(/*some image*/)
Button(onClick = { attack() }) {
Text(text = "ELIMINATE")
}
} else {
Text("No players nearby. Keep searching.")
Image(/*some OTHER image*/)
Button(onClick = { attack() }) { //This button should be DISABLED
Text(text = "ELIMINATE")
}
}
I have a boolean variable that becomes true if a player is within range, but I'm confused about how I can make the screen live update when this requirement is met. It should switch from disabled to enabled and vice-versa as an opposing player becomes in and out of range.
Here is the code we're using to set isPlayerNearby to true
private var endpointDiscoveryCallback: EndpointDiscoveryCallback = object :
EndpointDiscoveryCallback() {
override fun onEndpointFound(endpointId: String, info: DiscoveredEndpointInfo) {
toEndpointId = endpointId
// An endpoint was found. We request a connection to it.
Nearby.getConnectionsClient(context)
.requestConnection(getLocalUserName(), endpointId, connectionLifeCycleCallback)
.addOnSuccessListener {
run {
endpointFound()
//This is where we will set isPlayerNearby to true
Toast.makeText(applicationContext, endpointId, Toast.LENGTH_SHORT).show()
}
}
.addOnFailureListener { _ ->
// Toast.makeText(applicationContext, "Could Not Connect", Toast.LENGTH_SHORT).show()
}
}
override fun onEndpointLost(endpointId: String) {
Toast.makeText(applicationContext, "Endpoint Lost", Toast.LENGTH_SHORT).show()
}
}
Setting the boolean to false will change the screen, but it won't live update - the screen basically needs to be rebuilt to see the results. I believe we have to use states but I am not too familiar.

use LiveData or StateFlow
val isPlayerNearby = MutableLiveData(false)
...
run {
endpointFound()
isPlayerNerby.postValue(true)
Toast.makeText(applicationContext,endpointId,Toast.LENGTH_SHORT)
.show()
}
in Fragment observe our liveData:
viewModel.isPlayerNearby.observe(viewLifecicleOwner) { isPlayerNearby ->
if(isPlayerNearby) {
Text("Player $playerName is within range!")
Image(/*some image*/)
Button(onClick = { attack() }) {
Text(text = "ELIMINATE") }
}else {
Text("No players nearby. Keep searching.")
Image(/*some OTHER image*/)
Button(onClick = { attack() }) { //This button should be DISABLED
Text(text = "ELIMINATE")
}
}

Related

Navigate by checking login state with jetpack compose (Conditional Nested Nav graphs)

I am trying to implement a way to check authentication status from the room database before and navigate (Conditional navigation)
The requirement is as follows
I have a session manager with the following code
private val _sessionState = MutableStateFlow(SessionState.Loading)
val sessionState: StateFlow<SessionState> = _sessionState
init {
coroutineScope.launch {
sessionRepo.getActiveSession()
.collect { resource ->
_sessionState.value = when (resource) {
is Resource.Success -> {
if (resource.data == null) {
SessionState.InActive
} else {
SessionState.Active
}
}
else -> throw IllegalStateException("Failed to load session")
}
}
}
}
How can I check this with jetpack compose
If i understand your question correctly I would do it this way:
#Composable
fun App(mainViewModel: MainViewModel) {
val navController = rememberNavController()
LaunchedEffect(Unit) {
mainViewModel.checkAuth(navController)
}
NavHost(
navController = navController,
startDestination = RootNavigation.Home.route
) {
composable(RootNavigation.Home.route) {
SplashScreen()
}
loginGraph(navController)
workspaceGraph(navController)
}
}
At MainViewModel:
fun checkAuth(navController: NavHostController) {
viewModelScope.launch {
...//your code
if (resource.data == null)
navController.navigateAndReplaceStartRoute(RootNavigation.Login.route)
else navController.navigateAndReplaceStartRoute(RootNavigation.Workspace.route)
...//your code
}
}
Did I answer your question?

Android - How to detect "Power Save mode" in Huawei or other devices? [duplicate]

I am currently implement a feature where the users are requested to ignore battery optimisation for the application. The reason for doing so, is that the main functionality of the application is unfortunately drastically affected by power save mode.
To achieve my goal, I prompt the users by creating an Intent and setting the Action to ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS.
Although, before firing the Intent, I both check for isPowerSaveMode() and isIgnoringBatteryOptimizations() to ensure that I don't prompt the users when power save mode is not enabled; which is a requirement for the feature. The way I do so is by:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
boolean isPowerSaveMode = pm.isPowerSaveMode(); // always returns false for Huawei devices
This works fine for the most devices, but for Huawei devices, isPowerSaveMode() always returns false. Consequently, since the preconditions fail, the prompt is never shown.
Has anyone else possibly encountered this issue? If so, what did you do to solve it?
As a note, the same issue is also present in the Xamarin.Android SDK.
Some Chinese ROM like Huawei or Xiaomi didn't implement the standard API for power save mode query. But like other system settings, a state flag will be saved to database when user turn power save mode on/off.
So we can utilize this state flag to solve the compatibility problem. Also a specific intent will send by system when toggle power save mode, we can listen this intent action to monitor power save mode changing.
Below is the detailed kotlin code implementation for Huawei or Xiaomi devices.
object PowerManagerCompat {
private const val TAG = "PowerManagerCompat"
interface PowerSaveModeChangeListener {
/**
* will be called when power save mode change, new state can be query via [PowerManagerCompat.isPowerSaveMode]
*/
fun onPowerSaveModeChanged()
}
private val POWER_SAVE_MODE_VALUES = mapOf(
"HUAWEI" to 4,
"XIAOMI" to 1
)
private val POWER_SAVE_MODE_SETTING_NAMES = arrayOf(
"SmartModeStatus", // huawei setting name
"POWER_SAVE_MODE_OPEN" // xiaomi setting name
)
private val POWER_SAVE_MODE_CHANGE_ACTIONS = arrayOf(
"huawei.intent.action.POWER_MODE_CHANGED_ACTION",
"miui.intent.action.POWER_SAVE_MODE_CHANGED"
)
private const val monitorViaBroadcast = true
/**
* Monitor power save mode change, only support following devices
* * Xiaomi
* * Huawei
*/
fun monitorPowerSaveModeChange(context: Context, powerSaveModeChangeListener: PowerSaveModeChangeListener) {
if (Build.MANUFACTURER.toUpperCase(Locale.getDefault()) !in POWER_SAVE_MODE_VALUES.keys) {
Log.w(TAG, "monitorPowerSaveModeChange: doesn't know how to monitor power save mode change for ${Build.MANUFACTURER}")
}
if (monitorViaBroadcast) {
context.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
powerSaveModeChangeListener.onPowerSaveModeChanged()
}
}, IntentFilter().also {
for (a in POWER_SAVE_MODE_CHANGE_ACTIONS) {
it.addAction(a)
}
})
} else {
val contentObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
powerSaveModeChangeListener.onPowerSaveModeChanged()
}
}
for (name in POWER_SAVE_MODE_SETTING_NAMES) {
context.contentResolver.registerContentObserver(
Uri.parse("content://settings/system/${name}"), false, contentObserver)
}
}
}
/**
* Check the system is currently in power save mode
* #see [PowerManager.isPowerSaveMode]
*/
fun isPowerSaveMode(context: Context): Boolean {
if (Build.MANUFACTURER.toUpperCase(Locale.getDefault()) in POWER_SAVE_MODE_VALUES.keys) {
return isPowerSaveModeCompat(context)
}
val powerManager = context.getSystemService(Context.POWER_SERVICE) as? PowerManager
return powerManager?.isPowerSaveMode ?: false
}
private fun isPowerSaveModeCompat(context: Context): Boolean {
for (name in POWER_SAVE_MODE_SETTING_NAMES) {
val mode = Settings.System.getInt(context.contentResolver, name, -1)
if (mode != -1) {
return POWER_SAVE_MODE_VALUES[Build.MANUFACTURER.toUpperCase(Locale.getDefault())] == mode
}
}
return false
}
}
Each oem modifies the SDK to suit their needs . Huawei devices don't use the default power saver function , instead they use something called "Protected apps". Protected apps are set of apps which are allowed to run even when the screen is turned off. So that's the reason it always returns false . Its better to throw a intent to protected apps screen but there is no way to know if your app is added to the protected apps list.
What is protected apps ?
I've found a way to manually request current Huawei Power Mode state and receive change events by adding a custom action to the IntentFilter:
(Note tested only on Huawei P20 Lite (ANE-LX3) # EMUI 8.0.0)
// Manually request Power Save Mode:
public Boolean isPowerSaveMode(Context context) {
if (Build.MANUFACTURER.equalsIgnoreCase("Huawei")) {
return isPowerSaveModeHuawei(context);
} else {
return isPowerSaveModeAndroid(context);
}
}
#TargetApi(21)
private Boolean isPowerSaveModeAndroid(Context context) {
boolean isPowerSaveMode = false;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm != null) isPowerSaveMode = pm.isPowerSaveMode();
}
return isPowerSaveMode;
}
private Boolean isPowerSaveModeHuawei(Context context) {
try {
int value = android.provider.Settings.System.getInt(context.getContentResolver(), "SmartModeStatus");
return (value == 4);
} catch (Settings.SettingNotFoundException e) {
// Setting not found? Return standard android mechanism and hope for the best...
return isPowerSaveModeAndroid(context);
}
}
// Listening for changes in Power Save Mode
public void startMonitoringPowerSaveChanges(Context context) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mPowerSaveChangeReceiver != null) {
return;
}
// Register for PowerSaver change updates.
mPowerSaveChangeReceiver = new PowerSaveChangeReceiver();
// Registering the receiver
IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
// Add custom huawei action
filter.addAction("huawei.intent.action.POWER_MODE_CHANGED_ACTION");
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
filter.addAction(android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
}
context.registerReceiver(mPowerSaveChangeReceiver, filter);
}
}
#TargetApi(21)
class PowerSaveChangeReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
boolean isPowerSaveMode = false;
// Oh, Huawei...why don't you play by the same rules as everyone else?
if (intent.getAction().equals("huawei.intent.action.POWER_MODE_CHANGED_ACTION")) {
Bundle extras = intent.getExtras();
if ((extras != null) && extras.containsKey("state")) {
int state = intent.getExtras().getInt("state");
isPowerSaveMode = (state == 1); // ON=1; OFF=2
}
} else {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
isPowerSaveMode = pm.isPowerSaveMode();
}
Log.d("MyTag", "[powersavechange] isPowerSaveMode? " + isPowerSaveMode);
}
}
I have faced new the same problem while inmplementation handheld and wearable devices.
The only solution I found is to disable battery saver mode for all apps.
I would suggest to detect the result of your methods after disabling such mode for all apps. This bug appear only on Huawei. Awful vendor.
private void isPowerSaveModeHuaweiXiaomi(){
if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) {
try {
int value = android.provider.Settings.System.getInt(getContext().getContentResolver(), "POWER_SAVE_MODE_OPEN");
} catch (Settings.SettingNotFoundException e) {
Log.d("Valor modo bateria:", "Error");
}
}else if (Build.MANUFACTURER.equalsIgnoreCase("Huawei")){
try {
int value = android.provider.Settings.System.getInt(getContext().getContentResolver(), "SmartModeStatus");
} catch (Settings.SettingNotFoundException e) {
Log.d("Valor modo bateria:", "Error");
}
}
}
On new Huawei devices such as Huawei P30 lite for instance the solution to this question is unknown as for now (27.12.2021). calling getInt with the key "SmartModeStatus" will throw a key unknown exception. Therefore the best we can do is the following.
private string HuaweiPowerSaveModeSettingsName = "SmartModeStatus";
private int HuaweiPowerSaveModeValue = 4;
public bool IsBatterySaverEnabled
=> Build.Manufacturer?.ToUpper() == "HUAWEI" ? GetIsBatterySaverEnabledHuawei() : GetIsBatterySaverEnabledAllDevicesExceptHuawei();
private bool GetIsBatterySaverEnabledAllDevicesExceptHuawei()
{
return PowerManager.FromContext(Application.Context)?.IsPowerSaveMode ?? false;
}
private bool GetIsBatterySaverEnabledHuawei()
{
try
{
var mode = Settings.System.GetInt(Application.Context.ContentResolver, HuaweiPowerSaveModeSettingsName);
return HuaweiPowerSaveModeValue == mode;
} catch (Exception e)
{
return GetIsBatterySaverEnabledAllDevicesExceptHuawei();
}
}
For huawei vtr-al00, SmartModeStatus 1 could be ultra save mode or the normal mode. I've used reflection to handle this.
final int _HX = Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")?2
:Build.MANUFACTURER.equalsIgnoreCase("Huawei")?1
:0;
// “No Kotlin”
private boolean isPowerSaveModeCompat(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& powerManager.isPowerSaveMode()) { // hopefully...
return true;
}
if (_HX==0) {
return false;
}
else if (_HX==1) {
try {
int value = Settings.System.getInt(getContentResolver(), "SmartModeStatus");
CMN.debug("isPowerSaveModeCompat::huawei::"+value);
// value 4==Save Mode; 1==Ultra Save Mode==Normal Mode;
// ( tested on my huawei vtr-al00 )
if(value==4) {
return true;
}
if(value==1) {
// what if Ultra save mode???
// https://github.com/huaweigerrit
// https://github.com/SivanLiu/HwFrameWorkSource
// https://stackoverflow.com/questions/2641111/where-is-android-os-systemproperties
// Class sysProp= Class.forName("android.os.SystemProperties");
// Method sysProp_getBool = sysProp.getMethod("getBoolean", new Class[]{String.class, boolean.class});
// Object[] parms = new Object[]{"sys.super_power_save", false};
// CMN.debug("huawei::UltraPowerSave::", sysProp_getBool.invoke(null, parms));
// CMN.debug("huawei::UltraPowerSave::", getSystemProperty("sys.super_power_save"));
return "true".equals(getSystemProperty("sys.super_power_save"));
}
} catch (Exception e) {
CMN.debug(e);
}
}
else if (_HX==2){
try {
int value = Settings.System.getInt(getContentResolver(), "POWER_SAVE_MODE_OPEN");
CMN.debug("isPowerSaveModeCompat::xiaomi::"+value);
// dont have xiaomi. not tested.
return value==1;
} catch (Exception e) {
CMN.debug(e);
}
}
// else if...
return false;
}
// https://stackoverflow.com/questions/9937099/how-to-get-the-build-prop-values
public String getSystemProperty(String key) {
String value = null;
try {
value = (String) Class.forName("android.os.SystemProperties")
.getMethod("get", String.class).invoke(null, key);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
Java is just shorter kotlin, even with so many comments and dirty tests!
:)

How to display variables in ionic front-end using Cordova-Advanced.Geolocation and how to show location of network cell on a map

I have a problem with Ionic (5.0.1) and Angular. I would like to visualize some java variables in the home page, the variables concern information about the localization or about some parameters of the network cells. I found an example on how to install the "Cordova AdvancedGeolocation" plugin, but I can only view the default map, actually I wish I could access the plugin's variables to display in front-end.
I use this code in app.component.ts and then I change config.xml and, finally, I put "sample-map.html" in src field.
I see the map with location gps and network, but I don't understand how I could have access to variables Java and display them in front-end.
Thanks a lot.
import { Component, NgZone } from '#angular/core';
import { Platform } from '#ionic/angular';
import { SplashScreen } from '#ionic-native/splash-screen/ngx';
import { StatusBar } from '#ionic-native/status-bar/ngx';
import { Geolocation, Geoposition } from '#ionic-native/geolocation/ngx';
#Component({
selector: 'app-root',
templateUrl: 'app.component.html'
})
export class AppComponent {
AdvancedGeolocation: any;
currentLat: any;
currentLng: any;
watch: any;
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
// tslint:disable-next-line: no-shadowed-variable
private zone : NgZone,
private geolocation: Geolocation
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
if (this.platform.is('android')) {
this.platform.ready().then(() => {
this.AdvancedGeolocation.start((success) => {
//loading.dismiss();
// this.refreshCurrentUserLocation();
try {
var jsonObject = JSON.parse(success);
console.log("Provider " + JSON.stringify(jsonObject));
switch (jsonObject.provider) {
case "gps":
console.log("setting gps ====<<>>" + jsonObject.latitude);
this.currentLat = jsonObject.latitude;
this.currentLng = jsonObject.longitude;
break;
case "network":
console.log("setting network ====<<>>" + jsonObject.latitude);
this.currentLat = jsonObject.latitude;
this.currentLng = jsonObject.longitude;
break;
case "satellite":
//TODO
break;
case "cell_info":
//TODO
break;
case "cell_location":
//TODO
break;
case "signal_strength":
//TODO
break;
}
}
catch (exc) {
console.log("Invalid JSON: " + exc);
}
},
function (error) {
console.log("ERROR! " + JSON.stringify(error));
},
{
"minTime": 500, // Min time interval between updates (ms)
"minDistance": 1, // Min distance between updates (meters)
"noWarn": true, // Native location provider warnings
"providers": "all", // Return GPS, NETWORK and CELL locations
"useCache": true, // Return GPS and NETWORK cached locations
"satelliteData": false, // Return of GPS satellite info
"buffer": false, // Buffer location data
"bufferSize": 0, // Max elements in buffer
"signalStrength": false // Return cell signal strength data
});
});
} else {
// **For IOS**
let options = {
frequency: 1000,
enableHighAccuracy: false
};
this.watch = this.geolocation.getCurrentPosition({ enableHighAccuracy: true }).then((resp) => {
console.log("current location at login" + JSON.stringify(resp.coords));
// Run update inside of Angular's zone
this.zone.run(() => {
this.currentLat = resp.coords.latitude;
this.currentLng = resp.coords.longitude;
});
}, Error => {
console.log(Error);
}).catch(Error => {
console.log(Error);
}) ;
}
});
}
}

Implementing MVI Architecture in Android without Mosby

I am trying to implement MVI Architecture in Android, but don't want to use Mosby Library. I want to learn the basics first.
I am building a sample app where when I press a button, text in the textview changes(initially the text is something else). Here is the code for MainActivity and MainPresenter.
class MainActivity : AppCompatActivity(), MainContract.View {
lateinit var mPresenter: MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPresenter = MainPresenter()
mPresenter.attachPresenter(this)
bind()
}
#SuppressLint("CheckResult")
private fun bind() {
mPresenter.states().subscribe({ state ->
render(state)
}, {
Log.e("error", "Error is: ", it)
it.printStackTrace()
})
mPresenter.addIntents(intents())
}
override fun intents(): Observable<MainIntent> {
return Observable.merge(
initialIntent(),
clickIntent()
)
}
override fun render(state: MainViewState) {
btn_show.isEnabled = state.isEnabledButton
helloWorldTextView.text = state.message
loadingIndicator.visibility = if (state.isLoading) View.VISIBLE else View.GONE
}
private fun initialIntent(): Observable<MainIntent.InitialIntent> = Observable.just(MainIntent.InitialIntent)
private fun clickIntent(): Observable<MainIntent.ClickIntent> {
return btn_show.clicks().map { MainIntent.ClickIntent("Eureka") }
}
}
class MainPresenter : MainContract.Presenter {
private val intentsSubject: PublishSubject<MainIntent> = PublishSubject.create()
override fun states(): Observable<MainViewState> {
return statesObservable
}
private lateinit var view: MainContract.View
override fun attachPresenter(view: MainContract.View) {
this.view = view
}
#SuppressLint("CheckResult")
override fun addIntents(intents: Observable<MainIntent>) {
intents.subscribe(intentsSubject)
}
private val reducer =
BiFunction { previousState: MainViewState, result: MainResult ->
when (result) {
is MainResult.InitialResult.InFlight -> previousState.copy(
isLoading = true,
message = "Initial Result",
isEnabledButton = false
)
is MainResult.InitialResult.Success -> previousState.copy(
isLoading = true,
message = "Initial Success",
isEnabledButton = true
)
is MainResult.InitialResult.Error -> previousState.copy(
isLoading = false,
message = "Error Initially",
isEnabledButton = true
)
is MainResult.ClickedResult.Success -> previousState.copy(
isLoading = false,
message = System.currentTimeMillis().toString(),
isEnabledButton = true
)
is MainResult.ClickedResult.Error -> previousState.copy(
isLoading = false,
message = "Error Clicked",
isEnabledButton = true
)
is MainResult.ClickedResult.InFlight -> previousState.copy(
isLoading = true,
message = "Clicked In Flight",
isEnabledButton = false
)
}
}
private fun actionFromIntent(intent: MainIntent): MainAction {
if (intent is MainIntent.InitialIntent) {
return MainAction.InitialAction
} else if (intent is MainIntent.ClickIntent) {
return MainAction.ClickedAction("Hello")
} else {
return MainAction.InitialAction
}
}
private var actionProcessor: ObservableTransformer<MainAction, MainResult> = ObservableTransformer { actions ->
actions.publish { shared ->
Observable.merge<MainResult>(
shared.ofType(MainAction.InitialAction::class.java).compose(initialActionProcessor),
shared.ofType(MainAction.ClickedAction::class.java).compose(clickActionProcessor)
)
}
}
private val initialActionProcessor =
ObservableTransformer<MainAction.InitialAction, MainResult.InitialResult> { action: Observable<MainAction.InitialAction> ->
action.switchMap {
Observable.just("hello initially")
.map { MainResult.InitialResult.Success(it) }
.cast(MainResult.InitialResult::class.java)
.onErrorReturn { MainResult.InitialResult.Error(it.message!!) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.InitialResult.InFlight }
}
}
private val clickActionProcessor =
ObservableTransformer<MainAction.ClickedAction, MainResult.ClickedResult> { action: Observable<MainAction.ClickedAction> ->
Observable.just("Click").map { message ->
MainResult.ClickedResult.Success(message)
}.cast(MainResult.ClickedResult::class.java)
.onErrorReturn { MainResult.ClickedResult.Error("Error") }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.ClickedResult.InFlight }
}
private val statesObservable: Observable<MainViewState> = compose()
private fun compose(): Observable<MainViewState> {
return intentsSubject
.map {
actionFromIntent(it)
}
.compose(actionProcessor)
.scan(MainViewState.idle(), reducer)
.distinctUntilChanged()
.replay(1)
.autoConnect(0)
}
}
Problem is that only the Inital event is fired and nothing else. The code doesn't respond to clicks, render is called only initially once.
Also, if I remove the startWith{} from the actionProcessors code responds to clicks, but only once. After that, nothing happens.
Does anyone see issue with the code? I have been trying to get my head around this problem for a while now.
My previous reply:
It's not straight answer to your question. But if you implement what's below, you probably won't have the problem you actually asked about and you'll have easier MVI solution.
You probably try to merge https://speakerdeck.com/jakewharton/the-state-of-managing-state-with-rxjava-devoxx-us-2017, http://hannesdorfmann.com/android/mosby3-mvi-1 and https://medium.com/#oldergod/managing-state-with-rxjava-b0798a6c5757 ideas.
Take a look here: https://proandroiddev.com/taming-state-in-android-with-elm-architecture-and-kotlin-part-1-566caae0f706 - it's simpler. Part 1 and 2 should be enough.
I tried the 1st approach and was repulsed by initial complexity. In 2nd approach you don't have Action, Intent, Result, but Msg instead. It's simpler to reason about.
There's also new MVI course - but haven't checked it yet.
Current approach:
I tried mentioned Elm Architecture, but it is not complete. There are at least 2 problems:
Only one request can get through queue at one moment. Some RxJava
should do the trick (groupBy with 2 streams: ui, background
probably).
parallel requests would update the same state, so you should differentiate DataStates inside your UiState. So different state for different part of UI.
Before writing actual fixes we realised, this is not the way to go ATM: announced Composables could do the MVI trick: smooth and precise data transition to specific parts of UI.
Disclaimer: moderator removed my answer which WAS actual answer. Even more, my answer moved to comment is cut down, which makes it look unfinished. That's why this post emerged once again. After you read it dear moderator, you can remove disclaimer, thanks :)

How to check if ARCore lib/apk installed on device?

If I understand well, once ARCore 1.0 will be released on Google Play, it will be necessary to install it on the device in order to be able to run an ARCore app.
How to check if ARCore lib/apk is installed on device ?
Should be sufficient to do something like:
try {
arCoreSession = Session(this)
val config = Config(arCoreSession)
if (!arCoreSession.isSupported(config)) {
Logger.d("ARCore not installed")
} else {
arCoreSession.configure(config)
}
} catch (ex: Throwable) {
Logger.d("ARCore not installed")
}
This is what I'm using here for one my apps and works fine on devices with or wothout ARCore.
According to ARCore documentation 1.4.0, if optional it is important check its availability recursively and then install it:
void maybeEnableArButton() {
// Likely called from Activity.onCreate() of an activity with AR buttons.
ArCoreApk.Availability availability = ArCoreApk.getInstance().checkAvailability(this);
if (availability.isTransient()) {
// re-query at 5Hz while we check compatibility.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
maybeEnableArButton();
}
}, 200);
}
if (availability.isSupported()) {
mArButton.setVisibility(View.VISIBLE);
mArButton.setEnabled(true);
// indicator on the button.
} else { // unsupported or unknown
mArButton.setVisibility(View.INVISIBLE);
mArButton.setEnabled(false);
}
}
If already supported just check if ARCore is installed:
// Set to true ensures requestInstall() triggers installation if necessary.
private boolean mUserRequestedInstall = true;
// in onResume:
try {
if (mSession == null) {
switch (ArCoreApk.getInstance().requestInstall(this, mUserRequestedInstall)) {
case INSTALLED:
mSession = new Session(this);
// Success.
break;
case INSTALL_REQUESTED:
// Ensures next invocation of requestInstall() will either return
// INSTALLED or throw an exception.
mUserRequestedInstall = false;
return;
}
}
} catch (UnavailableUserDeclinedInstallationException e) {
// Display an appropriate message to the user and return gracefully.
return;
} catch (...) { // current catch statements
...
return; // mSession is still null
}
Sometimes it is easier to request this with Rx methodology. Here's the code:
private fun getArAvailabilityRx(context: Context): Single<ArCoreApk.Availability> {
return Single.fromCallable<ArCoreApk.Availability> {
ArCoreApk.getInstance().checkAvailability(context)
}.flatMap { availability ->
if (availability.isTransient) {
// `isTransient` means it hasn't finished loading value; let's request the value in 500 ms
getArAvailabilityRx(context).delaySubscription(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
} else {
Single.just(availability)
}
}.observeOn(AndroidSchedulers.mainThread())
}
Here's a little utility class I wrote (based originally on something from https://github.com/google/helloargdx).
It will perform all the checks and setup necessary, in order to ensure it is safe to launch a Session.
abstract class ArCheckFragment : Fragment() {
private var userRequestedInstall = true
abstract fun onCameraPermissionDeny()
abstract fun onArCoreUnavailable(availability: Availability)
abstract fun onArCoreInstallFail(exception: UnavailableException)
abstract fun onArCoreInstallSuccess()
override fun onResume() {
super.onResume()
performCheck()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_CAMERA_PERMISSION) {
for (i in permissions.indices) {
if (permissions[i] == Manifest.permission.CAMERA &&
grantResults[i] == PackageManager.PERMISSION_GRANTED
) {
checkArCore()
return
}
}
onCameraPermissionDeny()
}
}
/**
* Performs the whole check
*/
fun performCheck() {
if (requestCameraPermission()) {
checkArCore()
}
}
/**
* Requests the camera permission, if necessary.
* #return whether camera permission is already granted. If so, the permission won't be requested.
*/
private fun requestCameraPermission(): Boolean {
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
return true
}
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAMERA_PERMISSION)
return false
}
private fun checkArCore() {
if (!isResumed) {
return
}
val availability = ArCoreApk.getInstance().checkAvailability(activity)
if (availability.isTransient) {
requireView().postDelayed(AR_CORE_CHECK_INTERVAL) { checkArCore() }
return
}
when (availability) {
Availability.SUPPORTED_INSTALLED ->
onArCoreInstallSuccess()
Availability.SUPPORTED_APK_TOO_OLD,
Availability.SUPPORTED_NOT_INSTALLED ->
startArCoreInstallation()
else ->
onArCoreUnavailable(availability)
}
}
private fun startArCoreInstallation() {
try {
val installStatus =
ArCoreApk.getInstance().requestInstall(activity, userRequestedInstall)
when (installStatus) {
InstallStatus.INSTALLED -> onArCoreInstallSuccess()
InstallStatus.INSTALL_REQUESTED,
null ->
// Ensures next invocation of requestInstall() will either return
// INSTALLED or throw an exception.
userRequestedInstall = false
}
} catch (exception: UnavailableException) {
onArCoreInstallFail(exception)
}
}
companion object {
private const val REQUEST_CODE_CAMERA_PERMISSION = 1
private const val AR_CORE_CHECK_INTERVAL = 200L
}
}
You can subclass this Fragment and implement the abstract functions to receive callbacks on what the result of these checks is. Only in onArCoreInstallSuccess is it safe to create a Session.

Categories

Resources