So I'm trying to do the following in android xamarin.
When you press on a map element an infowindow is shown.When you press that window and an event is linked to the element the app goes to another fragment that is described by the action object. The problem is the moment I press the infowindow the whole app freezes and nothing happens. In the logs I can't see anything and the app stops at the following line:
pager.Adapter = pagerAdapter;
Added a breakpoint there and after saying "step over" the ide doesn't break anymore and the app freezes (no user interaction possible).
So let me start by giving all the relative code and a little explanation.
So first I'll show you what happens on the infowindow click. This happens on a SupportMapFragment that has it's own listener.
void GoogleMap.IOnInfoWindowClickListener.OnInfoWindowClick (Marker p0)
{
InfoPopup ip = CustomJsonConverter.Convert<InfoPopup> (p0.Title);
if (ip == null || ip.Goto == null || !(this.Activity is MainView))
return;
MainView main = (this.Activity as MainView);
p0.HideInfoWindow ();
switch (ip.Goto.type) {
case "InfoFragment":
Info info = InfoController.Items.Find (x => x.Index == ip.Goto.id);
if (info != null) {
main.RunOnUiThread (() => {
main.ShowInfosFragment ();
main.ShowInfoFragment (info);
});
}
break;
case "FaqFragment":
FaQ faq = FaQController.Items.Find (x => x.Index == ip.Goto.id);
if (faq != null) {
main.RunOnUiThread (() => {
main.ShowFaqsFragment ();
main.ShowFaqFragment (faq);
});
}
break;
}
}
I tested it with an action of InfoFragment which gives back an item so that's good. Then it goes to the main.ShowInfosFragment() which is where it freezes. So that method is really simple. This function is in the Activity holding the fragments (thanks Cheesebaron).
public void ShowInfosFragment(){
ShowFragment(1, new InfosFragment (){ InfoSelectedAction = ShowInfoFragment });
}
So the problem giving function is the following. This function is in the Activity holding the fragments (thanks Cheesebaron).
protected void ShowFragment(int index, Android.Support.V4.App.Fragment fragment, bool ignoreType = false){
RemoveSubMenu();
if (pagerAdapter.Count <= index || ignoreType || !(pagerAdapter.GetItem (index).GetType().Equals(fragment.GetType()))) {
pagerAdapter.AddFragment (index, fragment);
SetSubMenu (fragment);
pager.Adapter = pagerAdapter;
}
pager.SetCurrentItem (index, true);
DrawerButtonLayout ();
}
I've used this function a lot and it has always worked to move to the other fragments from the menu or at startup to set the mapfragment.
Anyone sees what's the problem with my code? Tried tons of things but can't seem to figure this one out with my friend google.
Thanks already for reading.
Kind regards,
Karl
Related
Consider a tabbar with "home" and "profile" buttons, when i click on either i switch between two pages, on the "home" page the user can navigate multiple times up in the navigationstack still having the focus on the "home" tab indicating that this is where the user came from.
Now, on iOS whenever the user clicks on "home" from high up in the navigationstack the user is popped to root and all is well, this is not the case on android however, on android the user has to pop one page at a time by clicking on the backbutton to get to the root.
Is this intended behavior, am i doing something wrong, does someone have a clue as to what i can do to get the desired behavior?
This is the intended behavior between iOS and Android .
If you need to make the Android has the same effect with iOS, you need to custom TabbedPageRenderer to achieve that. And the bottom tab bar effect can custom a FreshTabbedNavigationContainer . Last, we will use MessagingCenter to send message to Forms to pop to Root Page.
For example, CustomFreshTabbedNavigationContainer class:
public class CustomFreshTabbedNavigationContainer : FreshTabbedNavigationContainer
{
public CustomFreshTabbedNavigationContainer()
{
On<Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
MessagingCenter.Subscribe<object>(this, "Hi", (sender) =>
{
// Do something whenever the "Hi" message is received
PopToRoot(true);
});
}
}
Used in App.xaml.cs:
public App()
{
InitializeComponent();
var container = new CustomFreshTabbedNavigationContainer();
container.AddTab<FirstPageModel>("Home", default);
container.AddTab<ProfilePageModel>("Profile", default);
MainPage = container;
}
Now we will create a CustomTabbedPageRenderer in Android:
public class CustomTabbedPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener
{
public CustomTabbedPageRenderer(Context context) : base(context)
{
}
int previousItemId = 0;
bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)
{
base.OnNavigationItemSelected(item);
if (item.IsChecked)
{
if (previousItemId != item.ItemId)
{
previousItemId = item.ItemId;
}
else
{
Console.WriteLine("ok");
MessagingCenter.Send<object>(this, "Hi");
}
}
return true;
}
}
The effect:
Note: If need to have the same effect with the top Tabbar in Android, there is different code in CustomTabbedPageRenderer. You can have a look at this discussion.
I'm developing a codebar app for android with Xamarin and the Zxing library.
My objective was to have in the same view half of the screen with the codebar view, and the other half with the buttons to add or delete the scanned object to a list.
In MainActivity OnCreate function I have:
scanFragment = new ZXingScannerFragment();
FragmentTransaction fragmentTx = this.FragmentManager.BeginTransaction();
fragmentTx.Replace(Resource.Id.fragment, scanFragment);
fragmentTx.SetTransition(FragmentTransit.FragmentFade);
fragmentTx.Commit();
In ZXingScannerFragment OnCreate I have
frame = (FrameLayout)layoutInflater.Inflate(Resource.Layout.zxingscannerfragmentlayout, viewGroup, false);
return frame;
What I want it that, when the user scans something the view of the camera shut off, and then when the user has decided if wants to keep or discard the scanned object the camera shows again.
So I have a scann function called when the code is detected on MainActivity, who calls the OnPause method in the Zxing fragment and enables the buttons with this code:
var opts = new MobileBarcodeScanningOptions {
PossibleFormats = new List<ZXing.BarcodeFormat> {
ZXing.BarcodeFormat.All_1D,
}
};
scanFragment.StartScanning(result => {
if (result == null || string.IsNullOrEmpty(result.Text)) {
Toast.MakeText(this, "Scanning Cancelled", ToastLength.Long).Show();
return;
}
else
{
_player.Start();
RunOnUiThread(() => codBox.Text = result.Text);
RunOnUiThread(() => addBut.Enabled = true);
RunOnUiThread(() => delBut.Enabled = true);
RunOnUiThread(() => masBut.Enabled = true);
RunOnUiThread(() => menBut.Enabled = true);
RunOnUiThread(() => buttonDate.Enabled = true);
RunOnUiThread(() => finishBut.Enabled = false);
scanFragment.OnPause();
}
}, opts);
And then I have another function who calls the OnResume from Zxing fragment.
The OnPause function looks like:
base.OnPause ();
if (scanner != null)
{
frame.RemoveView(scanner);
if (!UseCustomOverlayView)
frame.RemoveView(zxingOverlay);
else if (CustomOverlayView != null)
frame.RemoveView(CustomOverlayView);
scanner.ShutdownCamera();
scanner = null;
}
The problem is:
With this code the OnPause function gives a "Only the original thread that created a view hierarchy can touch its views" Exception, but if I ignore it all works well some random time. I can take a code, camera vanishes, then add or remove object, and call camera again and all works fine 5... 10... 15 times in a row until I get a "Unhandled Exception: Java.Lang.NullPointerException" with no Idea were is being fired and no more info.
If I do something to prevent the hierarchy exception like:
if (scanner != null)
{
var myActivity = (MainActivity)this.Activity;
myActivity.RunOnUiThread(() =>
{
frame.RemoveView(scanner);
if (!UseCustomOverlayView)
frame.RemoveView(zxingOverlay);
else if (CustomOverlayView != null)
frame.RemoveView(CustomOverlayView);
});
scanner.ShutdownCamera();
scanner = null;
}
The camera vanishes, no exception is throwed but when I call the OnResume I get a static image of the last code detected.
At the end after many attempts I realized I was using a unmanaged crash as a correct behaviour of my app.
What I do is instead of calling the OnResume and OnPause functions (who have clearly objectives) of the Zxingfragment call directly scan function and stopscanning function.
I have an activity which hosts two fragments with only one shown at a time. Effectively the user, through different environmental conditions, should be able to toggle between the two at any given time.
There is a LoginFragment which is the first thing the user sees on login, and a LockoutFragment which may replace the LoginFragment after a user logs in and we see their account is locked (naturally).
That is the typical case, but there is a case in which LockoutFragment is presented first, if say, the user is using the app and their account is locked for some reason, and we re-open the host activity (LoginActivity), showing the LockoutFragment, but giving them a button to "Return to login", which toggles appearance of the LoginFragment (also naturally).
Thus, my goal is to allow a user to toggle between the two fragments, whichever is displayed first. My host activity uses the following functions to achieve this effect:
private void showLockoutFragment() {
if (mLockoutFragment == null) {
mLockoutFragment = new LockoutFragment();
}
transitionToFragment(FRAGMENT_LOCKOUT, mLockoutFragment);
}
private void showLoginFragment() {
if (mLoginFragment == null) {
mLoginFragment = new LoginFragment();
}
transitionToFragment(FRAGMENT_LOGIN, mLoginFragment);
}
private void transitionToFragment(String transactionTag, Fragment fragment) {
if (!getFragmentManager().popBackStackImmediate(transactionTag, 0)) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.setCustomAnimations(
R.animator.fade_in, R.animator.fade_out,
R.animator.fade_in, R.animator.fade_out);
ft.addToBackStack(transactionTag);
ft.replace(R.id.fragment_container, fragment, transactionTag);
ft.commit();
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// non configuration change launch
if (savedInstanceState == null) {
Bundle extras = getIntent().getExtras();
if (extras != null) {
// decide which fragment to show
boolean shouldLockout = extras.getBoolean(EXTRA_SHOULD_LOCKOUT);
if (shouldLockout) {
showLockoutFragment();
} else {
showLoginFragment();
}
} else {
showLoginFragment();
}
} else {
// retrieve any pre-existing fragments
mLoginFragment = (LoginFragment)getFragmentManager().findFragmentByTag(FRAGMENT_LOGIN);
mLockoutFragment = (LockoutFragment)getFragmentManager().findFragmentByTag(FRAGMENT_LOCKOUT);
}
}
These functions work together like a charm, with one exception: when, after initial launch of the app, a user
attempts log in,
is taken to the lockout fragment,
reorients the device, and
navigates back to the login fragment,
the login fragment is now present but invisible - as if the popEnter animation was never played. I know it is present because I can still interact with it.
It is also worth noting the following:
I have setRetainInstance(true) on both fragments
This only occurs when a user reorients the device from the lockout fragment
I have tried this on both a simulator and device running Lollipop with same results
Is it possible that the back stack is being corrupted after reorientation?
Thank you!
Ok, so it turns out the issue actually lies in my use of setRetainInstance. According to the docs for that method:
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. [emphasis mine]
While this appears rather cryptic to me, it seems that using setRetainInstance(true) on a fragment that is on the back stack could simply have unintended consequences. In my case, the fragment seemed to be retained, but its popEnter animation was never being called (post-rotation). Again, weird, but I guess just avoid that combination.
I tried including the relevant bit. I'm new at this and working with existing code. I hope someone can point me in the right direction.
Basically, say my website is www.mywebsite.com (lets call this 'A' since I can only post two hyperlinks) and you decide to do a search, so now the URL looks like this:
www.mywebsite.com/?s=notes (call it 'B')
You then get the SearchPage activity with the search results. Now say I tap a link from the search results. Example:
the url above/category/easy-news/ (call it 'C')
Website 'C' should load in PageActivity (which it does) but still loads up in SearchPageActivity which was initially showing 'B'.
On top of all of that, there seems to be a 3rd PageActivity (though it doesn't look like it opens a third time). One that also loads 'C' again. As mentioned, it is still the PageActivity, though. Pressing back closes the PageActivity and shows another PageActicity with the URL 'C'.
Closing that a second time takes me back to SearchPageActivity with the same URL 'C' which was initially 'B'. Pressing back one more time takes me back to 'B' within the same activity.
I don't seem to have this issue with the MainActivity, only in the SearchPage activity. In the MainActivity, things work fine. The code below is from SearchPage activity.
Sorry for the long winded message. I hope someone can help. Thanks.
Thanks.
private boolean isAppOrGamePage(String paramString) {
if ((paramString == null)
|| paramString.length() < 24
|| (!paramString.substring(0, 24).equals(
"http://www.mywebsite.com")))
return false;
String str1 = paramString.substring(24);
String str2 = str1.substring(0, 1 + str1.indexOf('/', 2));
if ((!mAppIdentifiers.contains(str2))
&& (!mGameIdentifiers.contains(str2)))
return false;
return true;
}
public boolean shouldOverrideUrlLoading(WebView paramWebView,
String paramString) {
if (MainActivity.DEBUG)
Log.e("shouldOverride", paramString);
if (Uri.parse(paramString).getHost() != null
&& (!Uri.parse(paramString).getHost()
.equals("myspace.com"))
&& (!paramString.contains("facebook.com"))
&& (!Uri.parse(paramString).getHost()
.contains("twitter.com"))
&& (!Uri.parse(paramString).getHost()
.equals("goo.gl"))
&& (!Uri.parse(paramString).getHost().contains("bit.ly"))
&& (!Uri.parse(paramString).getHost()
.contains("plus.google.com"))
&& (!Uri.parse(paramString).getHost()
.contains("youtube.com")))
if (isAppOrGamePage(paramString)) {
final Intent intent = new Intent(SearchActivity.this,
PageActivity.class);
intent.putExtra("app_url", paramString);
startActivity(intent);
} else
return false;
return false;
}
}
Return true when you open the other activity. You need to consume that call.
Returning true tells the webview that you have handled it, and don't want it to do anything with it.
I use $.mobile.changePage to go to another page. It success but after few second the page automatically back to the first page. How can i solve this problem?
here my code
$("#frmLogin").on("submit", function(e){
var u = $("#username",this).val();
var p = $("#password",this).val();
if(u != '' && p != ''){
if(window.method.login(u,p) == true){
$.mobile.changePage("home.html");
}else{
navigator.notification.alert("wrong combination");
}
}else{
navigator.notification,alert("wrong");
}
})
After your code runs, the form is continuing with the standard submit process, which involves reloading the page.
To stop that happening, your event handler needs to call preventDefault or return false.