React Native Navigation Sidebar and TabBar toggled navigation - react-navigation

I want to toggle between sidebar and tab navigation. I have it so that it currently switches between tabs and sidebar depending on the screen size, however since they are separate navigators it resets the navigation stack. Is there any way to persist the navigation stack when I change navigators?
If there is a way to have a Drawer and TabBar navigator with the same screens at the same time that would also solve my problem.
<Stack.Navigator
screenOptions={{ headerShown: false }}
mode="modal"
initialRouteName="WalkthroughScreen"
>
{deviceSize(layout.width) > 0 ||
(layout.width < 50 && Platform.OS === 'web') ? (
<Stack.Screen name="Root" component={DrawerNavigator} />
) : (
<Stack.Screen name="Root" component={BottomTabNavigator} />
)}

What I ended up doing was just using bottom navigation for the mobile app and drawer navigation for web and mobile web. So my code looks like this:
{Platform.OS === 'web' ? (
<Stack.Screen name="Root" component={DrawerNavigator} />
) : (
<Stack.Screen name="Root" component={BottomTabNavigator} />
)}
The reason this works and not the other way is there is since it just uses platform there is never a reason for the navigator to switch mid process. So I decided to give up on tabs and use a hamburger menu for mobile web with the side drawer and a permanent side drawer on larger screens.
So in my drawer navigator <DrawerNavigation.Navigator> I set drawerType like this
drawerType={deviceSize(layout.screenWidth) === 0 ? 'front' : 'permanent'}

Related

Xamarin Forms Shell - Navigation to old navigation stack rather than flyout page

I’m having trouble with my Xamarin Forms Shell app. I'm not sure if this is a bug or expected behaviour, can someone point me in the right direction.
I have an app with 2 pages in the Visual Shell Hierarchy:
Search & History
<FlyoutItem Title="Search" Icon="search.png">
<Tab Title="Search">
<ShellContent Route="searchpage">
<views:SearchPage />
</ShellContent>
</Tab>
</FlyoutItem>
<FlyoutItem Title="History" Icon="history.png">
<Tab Title="History">
<ShellContent>
<views:HistoryPage />
</ShellContent>
</Tab>
</FlyoutItem>
And several pages (let’s call them PageA, PageB and PageC) registered like so:
Routing.RegisterRoute("PageA", typeof(PageA));
Routing.RegisterRoute("PageB", typeof(PageB));
Routing.RegisterRoute("PageC", typeof(PageC)); (Oops, I should probably use nameof here)
Anyway, I start on the Search page and navigate to PageA like so:
Shell.Current.GoToAsync("PageA");
Because PageA is not in the visual hierarchy, this gives me a navigation stack like so:
"//searchpage/PageA"
Using the same relative navigation approach, I navigate to PageB then PageC, so my navigation stack is like so:
"//searchpage/PageA/PageB/PageC"
From PageC, I use the flyout menu to navigate to History, the history page opens fine.
Now on the history page, I use the flyout menu again and click the Search tab
But I'm not taken to the search page as expected, I'm taken back to PageC (taken back to my previous navigation stack).
From this page (PageC) if I use the flyout menu again and click the Search tab, it navigates correctly to the Search page.
How should this work and how can I stop it navigating to PageC when I select the Search tab from the flyout?
Thanks
(ps - I'm using Xamarin Forms 4.7 at present)
For those still looking for answers, an answer by Damir's Corner
await Shell.Current.GoToAsync($"///{tag}", false); //Checks the Navigation Stack Downward
The code above checks the current Navigation Stack, and searches for the Route Tag Downward (or inside the Navigation Stack) and then replaces the entire Stack with the Found Route.
Hope this helps for those looking for answers.
When jumping between different tabs (and by doing so different navigationstacks), Williams solution (Damir's Corner) worked for me.
My scenario:
Tab1 -> ListPage -> DetailsPage
Tab2 -> CreateStep1Page -> CreateStep2Page
After saving the entry in CreateSpecificPage, I navigate two times:
await Shell.Current.GoToAsync($"///{nameof(CreateStep1Page)}", false);
await Shell.Current.GoToAsync($"///{nameof(ListPage)}/{nameof(DetailsPage)}", true);
I will be raising this as a feature request when I get 5 minutes, but I have found a solution to this issue.
I made the last page (PageC) a Root page and removed it from my Routes Script. I did this by adding the following to the AppShell.Xaml like so:
<ShellItem Route="PageC">
<ShellContent ContentTemplate="{DataTemplate views:PageC}" />
</ShellItem>
and removing this code:
Routing.RegisterRoute("PageC", typeof(PageC));
Now when navigating to PageC, I am no longer pushing onto the stack with a relative navigation, I am now navigating to a root page like so:
await Shell.Current.GoToAsync("//PageC");
Before we navigate to PageC, we need to clear the current navigation stack. I tried PopToRootAsync, but this showed the SearchPage before navigating to PageC. I found the following code works:
// FYI - Navigation Stack: //SearchPage/PageA/PageB
var pageB = Shell.Current.Navigation.NavigationStack[2];
var pageA = Shell.Current.Navigation.NavigationStack[1];
Shell.Current.Navigation.RemovePage(pageB);
Shell.Current.Navigation.RemovePage(pageA);
// Now navigate to our route page, PageC.
await Shell.Current.GoToAsync("//PageC");
I will be using a slightly more elegant way of getting the pages rather than hard coding the index, but thought this was the easiest way to demonstrate what was happening.
The next time I navigate to the SearchPage now, I get the SearchPage
I think this feature is intentional. One thing to understand is that as mentioned in the docs, every Tab contains separate navigation stack, Shell.Current.Navigation points to the currently active/visible Tab navigation stack. So what you can do is actually quite simple.
You just override Shell.OnNavigating event, and from remove every page except root one from the current navigation stack only if you are navigating between different route trees.
Like, from //route1/page1 to //route2/page2.
You can adapt following code per your use case.
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
Setup.RegisterRoutes(this);
}
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
if (args.Current != null && args.Target != null && args.Current.Location.OriginalString.StartsWith("//") && args.Target.Location.OriginalString.StartsWith("//")) {
var currentRoot = args.Current.Location.OriginalString.TrimStart('/').Split('/').First();
var targetRoot = args.Target.Location.OriginalString.TrimStart('/').Split('/').First();
// we are navigating between tabs
if (!string.Equals(currentRoot, targetRoot, StringComparison.OrdinalIgnoreCase) && Navigation.NavigationStack.Count > 1) {
for (var i = Navigation.NavigationStack.Count - 1; i > 0; i--) {
Navigation.RemovePage(Navigation.NavigationStack[i]);
}
}
}
}
}

Nativescript Vue: SearchBar on Android in focus with keyboard visible when page loads

In Android, the page loads with the keyboard expanded and the focus is on the search bar.
I am trying to keep the focus from not being there on page load. The relevant code is :
<StackLayout height="100%"
class="main-container"
>
<SearchBar ref="searchBars"
class="search-bar"
#loaded="onSearchLoaded($event)"
#textChange="onTextChanged($event)"/>
I have tried adding:
mounted() {
this.$refs.searchBars.nativeView.dismissSoftInput();
}
but this has not worked. I also tried adding this.$refs.searchBars.nativeView.dismissSoftInput()
to the #loaded event function of the searchBar.
Try this
<SearchBar #loaded="onSearchBarLoaded($event)" />
And add a method:
onSearchBarLoaded: function(event) {
if (event.object.android) {
setTimeout(() => {
event.object.dismissSoftInput();
event.object.android.clearFocus();
}, 0);
}
}
I have also tried the above method and failed. So I've did a small trick to tackle the situation.
I've added a dummy search bar and set the visibility to hidden. So the focus will shift to the dummy search bar! Here is my code.
<SearchBar
v-model="searchPhrase"
#textChange="onSearch"
#submit="onSearch"
backgroundColor="white"
#clear="clearSearch" />
<!--Dummy searchbar -->
<SearchBar v-show="false" />
You can use a method to focus on what ever you want.
searchBars() {
this.$refs.searchBars.nativeView.focus();
},

Hamburger menu AutomationId for UI Testing

I'm trying to run a UI Test on a page (About.xaml).
Below are the steps to get to the page when the application loads.
Launch the Login Screen
User enters username
User enters password
User clicks on the Login Button
User lands on the Home Page in an AppShell.
User clicks on the Hamburger menu
User clicks on the About Menu in the Flyout Menu Items.
My question is, how do I set the Automation Id for the Hamburger Menu (Flyout Menu) of the AppShell?
Here's the UI Test Case.
[Test]
public async Task AboutPage_UITest()
{
//Arange
app.EnterText("UsernameEntryId", "user1");
app.EnterText("PasswordEntryId", "Abc#123");
//Act
app.DismissKeyboard();
app.Tap(x => x.Marked("LoginButtonId"));
app.Tap(x => x.Marked("AppShellId"));
//app.Tap(c => c.Class("OverflowMenuButton")); I tried this as well but no luck.
await Task.Delay(30000);
app.Tap(x => x.Marked("AboutId"));
//Assert
var appResult = app.Query("EmailId").First(result => result.Text == "abc#example.com");
Assert.IsTrue(appResult != null, "Label is not displaying the right result!");
app.Screenshot("About Page");
}
In the AppShell.xaml, here's the top section.
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
FlyoutHeaderBehavior="CollapseOnScroll"
Shell.ItemTemplate="{StaticResource FlyoutTemplate}"
Shell.MenuItemTemplate="{StaticResource FlyoutTemplate}"
FlyoutBackgroundColor="WhiteSmoke"
Navigating="OnNavigating"
Navigated="OnNavigated"
AutomationId="AppShellId"
x:Class="DemoApp.AppShell">
Welcome to SO!
After researching that , you can use follow ways to set the AutomationId for the Hamburger Menu.
In AppShell.xaml.cs:
public AppShell()
{
InitializeComponent();
FlyoutIcon.AutomationId = "FlyoutIcon";
//or
FlyoutIcon.SetValue(AutomationProperties.NameProperty, "FlyoutIcon");
}
Note: Also need to set a value for FlyoutIcon(Such as: FlyoutIcon="xxx.png") make it work, otherwise above code will occur error System.NullReferenceException: 'Object reference not set to an instance of an object.'.
In addition, here it the way to go to About Page without tapping the Hamburger menu.
You can add TabBar to AppShell.xaml, and defining the About page and other page inside it.
Such as follow:
<TabBar>
<Tab Title="About"
AutomationId="About"
Icon="tab_about.png">
<ShellContent>
<local:AboutPage />
</ShellContent>
</Tab>
<Tab Title="Browse"
AutomationId="Browse"
Icon="tab_feed.png">
<ShellContent>
<local:ItemsPage />
</ShellContent>
</Tab>
</TabBar>
Then in Test method can navigate to your wanted page as follow:
app.Tap(x => x.Marked("About"));
Or, directly in Xaml, like this:
<Shell.FlyoutIcon>
<FontImageSource AutomationId="THE_ID_YOU_WANT" ... other props />
</Shell.FlyoutIcon>
You can also use FileImageSource or UrlImageSource or whatever kind you need. The important thing is that the AutomationId goes on the ImageSource tag itself.
Annoyingly, you don't seem to be able to set the AutomationId without manually defining an icon. And the Id it gives you is not consistent every run (Sometimes it is "OK" and sometimes "Open navigation drawer" - so this is the best way to get a consistent Id.

How to make image as toggle button to show two different images in react native

I am doing react native project. In that, I have to show custom image like toggle button, In that, for OFF one image and ON another image I have to display, And in that, two components should have to display according to ON/OFF states.
I am new to this domain.
I have knowledge to set image touchable/Onpress, but, How to set custom image and act like toggle according to that components switch.
this.toggleAction() = () => {
//switching components for ON/OFF states
}
<TouchableHighlight >
<Image style={styles.imagestyle}
source={require('./ic_toggle_on.png')} />
onPress={() => this.toggleAction()}>
</TouchableHighlight>
Any suggestions?
You need to store the current state of Toggle button in a state variable:
this.state={
toggle:false
}
then you need to change the state in the onPress method of your TouchableOpacity.
After that you just need conditional rendering to show the different images
render(
<TouchableOpacity onPress={()=>this.setState({toggle:!this.state.toggle})}>
{
this.state.toggle==true?
<Image src={ YOUR TOGGLE ON SOURCE}/>
:
<Image src={ YOUR TOGGLE Off SOURCE}/>
}
)

How to align title text in ActionBar to the right instead of the default left?

I am creating an Arabic(RTL) app with NativeScript. I want the action bar to be sorted in rtl direction so the title is the right and menu options is to the left.
I tired using css and horizontalAlignment but nothing worked.
How can I achieve this?
Thank you.
Example
<Page xmlns="http://schemas.nativescript.org/tns.xsd">
<Page.ActionBar>
<ActionBar >
<Label text="TITLE"/>
</ActionBar>
</Page.ActionBar>
</Page>
And with css u can set to right side with or other styling of ActionBar
ActionBar Label{
text-align:right;
horizontal-align:right;
}
If you need RTL support due to language vs just "alignment" I would convert the entire app to RTL using android.view .View.LAYOUT_DIRECTION_RTL:
This code will make the ActionBar ( and other components ) across the app to RTL
# app.module.ts
import {
android as Android,
AndroidActivityEventData,
AndroidApplication } from '#nativescript/core/application';
Android.addEventListener(AndroidApplication.activityCreatedEvent, (event: AndroidActivityEventData) => {
event.activity.getWindow().getDecorView().setLayoutDirection(android.view .View.LAYOUT_DIRECTION_RTL);
});

Resources