Xamarin Forms Maps, after adding a pin, moveToRegion doesnt work - xamarin

I have implementation that in my OnAppearing() method, if some cases fulfilled (App.ComebackFromPages) to add pin to the map (App.SelectedPin) to a map.
after that I want to moveToRegion to that pin and zoom it.
Current state: App adds the pin, I can see it on the map, but map.MoveToRegion doesnt work.
Desired state: App adds the pin, and zoom in to that particular pin.
Here is the OnAppearing method:
protected async override void OnAppearing()
{
base.OnAppearing();
if (App.ComebackFromPages && App.SelectedPin != null)
{
if (!map.CustomPins.Any(x => x.IdObjectInstance == App.SelectedPin.IdObjectInstance))
{
map.CustomPins.Add(App.SelectedPin);
App.MapPins.Add(App.SelectedPin);
}
map.MoveToRegion(MapSpan.FromCenterAndRadius(App.SelectedPin.Position, Distance.FromMiles(0.5)));
App.ComebackFromPages = false;
}
}
My guess is that maybe OnApperiang method is async? and its not on the main thread?

I solved this issue adding await Task.Delay(500) between adding a pin to map and calling the method map.MpveToRegion

Related

Xamarin Android: Calling Invalidate() inside overridden OnDraw method

I am creating a custom renderer, that needs to display whatever I have rendered in my Vulkan engine. For this I have a VulkanSurfaceView, which inherits from MetalKit.MTKView on iOS, and from Android.Views.SurfaceView and ISurfaceHolderCallback on Android.
For iOS I can simply do this, which will draw a new frame continually, as long as the view is in focus:
public class VulkanSurfaceView : MTKView, IVulkanAppHost
{
...
public override void Draw()
{
Renderer.Tick();
base.Draw();
}
}
However, on Android I have to do this, where I call Invalidate() from within the OnDraw method, else it is only called once. I think this code smells a bit, and I am not sure, if this is the "good" way of doing it. Is my solution okay? If not, does anyone have a better idea?
public class VulkanSurfaceView : SurfaceView, ISurfaceHolderCallback, IVulkanAppHost
{
...
protected override void OnDraw(Canvas? canvas)
{
Renderer.Tick();
base.OnDraw(canvas);
Invalidate();
}
}
Did you try calling setWillNotDraw(false) in your surfaceCreated method ?
Refer the link
Thank you to #ToolmakerSteve.
I created a Timer where I call Invalidate() if a new frame has been requested (by me via a simple bool). For anyone interested I do it like so:
protected override void OnDraw(Canvas? canvas) // Just to show the updated OnDraw-method
{
Renderer.Tick();
base.OnDraw(canvas);
}
public void SurfaceCreated(ISurfaceHolder holder)
{
TickTimer = new System.Threading.Timer(state =>
{
AndroidApplication.SynchronizationContext.Send(_ => { if (NewFrameRequested) Invalidate(); }, state);
try { TickTimer.Change(0, Timeout.Infinite); } catch (ObjectDisposedException) { }
}, null, 0, Timeout.Infinite);
}
For now it is very simple, but it works and will probably grow. The reason for my initial bad framerate with this method was a misunderstanding of the "dueTime" of the Timer (see Timer Class), which I though was the framerate sought. This is actually the time between frames, which seems obvious now.
As #Bhargavi also kindly mentioned you need to set "setWillNotDraw(false)" if OnDraw is not being called when invalidating the view.

Xamarin forms switch events lead to infinte loop - Group switches

I am trying to develop an app using Xamarin.Forms. At a certain point I am trying to have multiple switches that are grouped. This is to say that when one switch is toggled, every other switch needs to be untoggled and, at the same time, there needs to be at least one switch always toggled. This is to say that tapping on a switch that is already toggled should not change anything.T
Now my problem is that Xamarin.forms Toggled event for switches can be fired from the UI, but is also fired programmatically. I thought I had found a way around this problem, but still by doing:
-If the switch was turned on, turn off all others and do application stuff.
-else if a switch was turned off, check if there are any others that are on. If not, turn the switch back on. If yes, do nothing.
A sample code for two switches could be:
private void OnFirstToggled(object sender, EventArgs args)
{
if(FirstSwitch.isToggled)
{
//Application stuff.
SecondSwitch.isToggled = false;
}
else if (!SecondSwitch.isToggled)
{
FirstSwitch.isToggled = true;
}
}
private void OnSecondToggled(object sender, EventArgs args)
{
if(SecondSwitch.isToggled)
{
//Application stuff.
FirstSwitch.isToggled = false;
}
else if (!FirstSwitch.isToggled)
{
SecondSwitch.isToggled = true;
}
}
This solution results in an infinite loop when an already toggled switch is tapped. In fact, the isToggled property of the switch alternates between true and false infinitely. However when debugging the other event never seems to be fired (or at least my debugger does not see it). This is why I don't understand where the isToggled property is changed after that first tap.
I know this is probably a very simple issue, but I cannot seem to find the solution somewhere online. Can anyone see the problem or recommend a better, common way to implement this?
I write a simple solution to you to always keep one Switch open from a Switch group.
Let's first add three switch for test, make sure these Switch will fire the same event of Toggled:
<StackLayout>
<!-- Place new controls here -->
<Switch Toggled="Switch_Toggled" x:Name="FirstSwitch"/>
<Switch Toggled="Switch_Toggled" x:Name="SecondSwitch"/>
<Switch Toggled="Switch_Toggled" x:Name="ThirdSwitch"/>
</StackLayout>
In the code behind, I add those Switches into a list, and loop them in Switch_Toggled event to open/close the Switches:
public partial class MainPage : ContentPage
{
List<Switch> switchList;// To store all your Switches
bool isLooping; //To make sure the Switch_Toggled metod not fired a second time during one toogle event
public MainPage()
{
InitializeComponent();
switchList = new List<Switch>();
switchList.Add(FirstSwitch);
switchList.Add(SecondSwitch);
switchList.Add(ThirdSwitch);
isLooping = false;
}
private void Switch_Toggled(object sender, ToggledEventArgs e)
{
//To make sure the Switch_Toggled metod not fired a second time during one toogle event
if (isLooping == true)
{
return;
}
isLooping = true;
Switch clickSwitch = sender as Switch;
clickSwitch.IsToggled = true;
foreach (var tempSwitch in switchList)
{
if (tempSwitch != clickSwitch)
{
if (tempSwitch.IsToggled == true)
{
tempSwitch.IsToggled = false;
}
}
}
isLooping = false;
}
}
You can try this solution and feel free to ask me any question if you don't understand.
Your problem are the two else blocks. Take in account that you're toggling it on anyway.

Can I change the UI from a method that gets data (not async) that has been called with await?

I have this code:
protected override async void OnAppearing()
{
base.OnAppearing();
Subscribe();
vm.IsBusy = true;
if (Change.firstTimeCardsTab == true)
{
SetButtons(Settings.cc.ShortText());
await Task.Run(() => GetOnAppearingData());
}
}
private void GetOnAppearingData()
{
App.DB.GetData();
AddDetailSection();
SetPageDetails();
Change.firstTimeCardsTab = false;
vm.IsBusy = false;
}
vm.IsBusy = true; shows an activity indicator on the page and vm.IsBusy = false; would I hoped turn it off.
However when I run the code I get a message saying:
UIKit Consistency error: you are calling a UIKit method that can only
be invoked from a UI thread.
Can someone give me advice on how I can set the activity indicator to show, then get the data and then set it to not show? Note that AddDetailSection(); adds some elements to the XAML. Could this be the problem?
UI can be manipulated only from the main thread. There is a method Device.BeginInvokeOnMainThread(Action) that can help to solve your issue, more can be found in the official documentation.
Just wrap all UI related actions by it:
Device.BeginInvokeOnMainThread(() => {
SetPageDetails();
// Any other actions
});

How to handle back button when at the starting destination of the navigation component

I've started working with the new navigation component and I'm really digging it! I do have one issue though - How am I supposed to handle the back button when I'm at the starting destination of the graph?
This is the code I'm using now:
findNavController(this, R.id.my_nav_host_fragment)
.navigateUp()
When I'm anywhere on my graph, it's working great, it send me back, but when I'm at the start of it - the app crashes since the backstack is empty.
This all makes sense to me, I'm just not sure how to handle it.
While I can check if the current fragment's ID is the same as the one that I know to be the root of the graph, I'm looking for a more elegant solution like some bool flag of wether or not the current location in the graph is the starting location or not.
Ideas?
I had a similar scenario where I wanted to finish the activity when I was at the start destination and do a regular 'navigateUp' when I was further down the navigation graph. I solved this through a simple extension function:
fun NavController.navigateUpOrFinish(activity: AppCompatActivity): Boolean {
return if (navigateUp()) {
true
} else {
activity.finish()
true
}
}
And then call it like:
override fun onSupportNavigateUp() =
findNavController(R.id.nav_fragment).navigateUpOrFinish(this)
However I was unable to use NavigationUI as this would hide the back arrow whenever I was at the start destination. So instead of:
NavigationUI.setupActionBarWithNavController(this, controller)
I manually controlled the home icon:
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_navigation_back)
Override onBackPressed in your activity and check if the current destination is the start destination or not.
Practically it looks like this:
#Override
public void onBackPressed() {
if (Navigation.findNavController(this,R.id.nav_host_fragment)
.getCurrentDestination().getId() == R.id.your_start_destination) {
// handle back button the way you want here
return;
}
super.onBackPressed();
}
You shouldn't override "onBackPressed", you should override "onSupportNavigateUp" and put there
findNavController(this, R.id.my_nav_host_fragment)
.navigateUp()
From the official documentation:
You will also overwrite AppCompatActivity.onSupportNavigateUp() and call NavController.navigateUp
https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing
In Jetpack Navigation Component, if you want to perform some operation when fragment is poped then you need to override following functions.
Add OnBackPressedCallback in fragment to run your special operation when back present in system navigation bar at bottom is pressed .
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
//perform your operation and call navigateUp
findNavController().navigateUp()
}
}
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
}
Add onOptionsItemMenu in fragment to handle back arrow press present at top left corner within the app.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
//perform your operation and call navigateUp
findNavController().navigateUp()
return true
}
return super.onOptionsItemSelected(item)
}
If there is no special code to be run when back is pressed on host fragment then use onSupportNavigateUp in Activity.
override fun onSupportNavigateUp(): Boolean {
if (navController.navigateUp() == false){
//navigateUp() returns false if there are no more fragments to pop
onBackPressed()
}
return navController.navigateUp()
}
Note that onSupportNavigateUp() is not called if the fragment contains onOptionsItemSelected()
As my back button works correctly, and using NavController.navigateUp() crashed on start destination back button. I have changed this code to something like this. Other possibility will be to just check if currentDestination == startDestination.id but I want to close Activity and go back to other Activity.
override fun onSupportNavigateUp() : Boolean {
//return findNavController(R.id.wizard_nav_host_fragment).navigateUp()
onBackPressed()
return true
}
/** in your activity **/
private boolean doubleBackToExitPressedOnce = false;
#RequiresApi(api = Build.VERSION_CODES.M)
#Override
public void onBackPressed() {
int start = Navigation.findNavController(this, R.id.nav_host_fragment).getCurrentDestination().getId();
if (start == R.id.nav_home) {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(MainActivity.this, "Press back again to exits", Toast.LENGTH_SHORT).show();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
doubleBackToExitPressedOnce = false;
}
}, 2000);
} else {
super.onBackPressed();
}
}
If you mean the start of your "root" navigation graph (just incase you have nested navigation graphs) then you shouldn't be showing an up button at all, at least according to the navigation principles.
Just call this in your back button Onclick
requireActivity().finish()

Start specific view of Gluon App from a notification

I set up an alarm to show a corresponding Notification. The PendingIntent of the Notification is used to start the Gluon App main class. To show a View other than the homeView, I call switchView(otherView) in the postInit method. OtherView is shown, but without AppBar. While it's possible to make the AppBar appear, I wonder if this is the right approach.
#Override
public void postInit(Scene scene) {
// additional setUp logic
boolean showReadingView = (boolean) PlatformProvider.getPlatform().getLaunchIntentExtra("showReadingView", false);
if (showReadingView) {
switchView(READING_VIEW);
}
}
When triggering anything related to the JavaFX thread from another thread, we have to use Platform.runLater().
Yours is a clear case of this situation: the Android thread is calling some pending intent, and as a result, the app is started again.
This should be done:
#Override
public void postInit(Scene scene) {
// additional setUp logic
boolean showReadingView = (boolean) PlatformProvider.getPlatform().getLaunchIntentExtra("showReadingView", false);
if (showReadingView) {
Platform.runLater(() -> switchView(READING_VIEW));
}
}

Resources