My goal is to to have a LoginScreen from which I can navigate to an InternalScreen. The InternalScreen should have/be a bottom navigation bar that can navigate to multiple other screens/routes in the internal space.
This is what I imagined my NavGraph was supposed to look like:
- LoginScreen
- internal space
- InternalScreen with BottomNavigation
- some fragment
- some other fragment
My idea was to create a Scaffold with a BottomNavigationBar in the InternalScreen composable but I do not no where to put it in my NavGraph since said NavGraph also has to contain the different routes for the BottomNavigationBar.
How should I approach this? I am sorry if this has already been answered, I couldn't find anything about this particular case.
I think the login screen/flow must be part of the application navigation flow. In summary, your application must react to a isLoggedIn state, which should be global, and in case of the user is not logged in, the login screen must be displayed.
This is what I did:
#Composable
fun MainNavigation(
viewModel: MainViewModel,
navController: NavHostController,
) {
val auth = viewModel.auth
val initialRoute =
if (auth.isLoggedIn()) BooksFeature.route else LoginFeature.route
AnimatedNavHost(
navController,
startDestination = initialRoute
) {
loginGraph(auth, navController)
booksGraph(auth, navController)
settingsGraph(navController)
}
}
The MainNavigation composable is the root of my app (which is called in setContent at MainActivity). Each feature of the app has a navigation graph. Like booksGraph:
fun NavGraphBuilder.booksGraph(
auth: Auth, // this is a simple class which
// knows if the user is logged in
navController: NavHostController
) {
navigation(
route = BooksFeature.route,
startDestination = BooksList.route,
) {
composable("ScreenA") {
ScreenA()
}
...
}
}
In my activity (I'm using just one activity), I'm observing the login state and redirecting to the login screen properly.
private fun launchLoginObserver() {
lifecycleScope.launch(Dispatchers.Main) {
mainViewModel.isLoggedIn.collect { isLoggedInState ->
if (isLoggedInState == false) {
navigationController.navigate(LoginScreen.route) {
popUpTo(0) // reset stack
}
}
}
}
}
If you want to take a look into the full implementation, here is the link for my repository.
Related
How to get initial keyboard focus on an Android compose app?
My view looks like
Parent { Child { Button} }
I tried implementing it in the Parent composable function....
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
The following code woirks like a charm....
fun Modifier.requestInitialFocus() = composed {
val first = remember { FocusRequester() }
LaunchedEffect(first) {
delay(1)
first.requestFocus()
}
focusRequester(first)
}
Original:
This error does not happen when implementing it in the composable function, where the target element is a direct child....
So implementing it in the Child seems to be a solution....
I am trying to replace the NavHost with AnimatedNavHost, but having an issue.
Using NavHost I have this structure:
#Composable
fun AppNavHost(...){
val navController = rememberNavController()
NavHost(navController = navController, ...) {
composable("1") { Screen1() }
composable("2") { Feature1NavHost() }
}
}
In Feature1NavHost, I have another NavHost that takes control of the feature navigation. So, Screen-1 calls “2” and pops out of the stack. Everything works fine here.
Using AnimatedNavHost, when I press back in the first screen of Feature1NavHost, the display blinks and the application does not close. I have to press a lot to close the app.
In my application I have a main.qml which has a navigation pane that goes to homepage.qml and from there to profilepage.qml. When I come back from profilepage to homepage I need to trigger a function in home page. I noticed that whenever I pop back I get a call onPopTransitionEnded in the main page. Since homepage is pushed from main.qml there is no navigation pane on homepage and I cant access onPopTransitionEnded on homepage. Below are the sample structures of my 3 qml views.
main.qml
NavigationPane {
id: nav
peekEnabled: false
onPopTransitionEnded: {
console.log("POP PAGE from main");
if(page.objectName=="newProfilePage")
{
//I tried to access the function using the homepage id but didnt work
menuScreenPage.reloadView(); // This doesnt work, shows error unknown symbol menuScreenPage
}
page.destroy();
}
Page {
id: mainPage
Container {
//some code
}
onCreationCompleted: {
//Some code and then push to homepage
nav.push(homePageDefenition.createObject());
}
}
}
homepage.qml
Page {
id: menuScreenPage
objectName: "menuScreenPage"
function reloadView() //This is the function that is needed to be called on page pop from profile page
{
//some code
}
Container {
//some code
Button { //a button to push to profile page
id:pushButton
horizontalAlignment: HorizontalAlignment.Right
verticalAlignment: VerticalAlignment.Bottom
onClicked: {
console.log("I was clicked!")
nav.push(profilePageDefinition.createObject());
}
}
}
}
profilepage.qml
Page {
id: newProfilePage
objectName: "newProfilePage"
Container {
//some code
Button { //a button to pop to home page
id:popButton
horizontalAlignment: HorizontalAlignment.Right
verticalAlignment: VerticalAlignment.Bottom
onClicked: {
console.log("I was clicked!")
nav.pop();
}
}
}
}
So is there a way that I can access the function of homepage.qml from main.qml? Or is there any other function like onPopTransitionEnded which I can access on homepage.qml itself when I pop from profilepage? Please advice.
Seems that you created unnamed object with this line:
homePageDefenition.createObject()
If you want to access it later, you should save it in some property, for ex.
property var myHomePage: null
...
myHomePage = homePageDefenition.createObject()
nav.push(myHomePage )
...
myHomePage.reloadView();
Keep in mind that "menuScreenPage" is local name (id), it works only inside homepage.qml and nobody can access it beyond that file.
UPD
You can even use such code:
page.reloadView(); // Use local variable "page" instead of internal id "menuScreenPage"
Setup looks like this:
LoginView MvxViewController
MainView MvxTabBarViewController
-Tab 1
- View1 (MvxViewController)
-Tab 2
- View1 (MvxViewController)
-Tab 3
- View1 (MvxViewController)
On View1 a I have a Tableview (List), will be filled always differently - depends on the tab.
Everything works fine so far. The problem I face now is, that when I'm in View1 and press the "Back" Button on the NavigationController I will get back to the "LoginView" instead the "MainView" (Rootview where the tabs are).
I found following command this.NavigationController.PopToRootViewController(true); but I didn't find the right place to use it. (If it's even the right way)
I used this project to get the idea behind https://github.com/slodge/MvvmCross-Tutorials/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Touch/Views/TabBarController.cs
Any help appreciated!
EDIT:
I solved the problem now, by removing following code (commented section removed):
public class MyPresenter : MvxModalSupportTouchViewPresenter, ITabBarPresenterHost
{
public MyPresenter(UIApplicationDelegate applicationDelegate, UIWindow window)
: base(applicationDelegate, window)
{
}
protected override UINavigationController CreateNavigationController(UIViewController viewController)
{
var toReturn = base.CreateNavigationController(viewController);
toReturn.NavigationBarHidden = false;
return toReturn;
}
public ITabBarPresenter TabBarPresenter { get; set; }
public override void Show(IMvxTouchView view)
{
//if (TabBarPresenter != null && view != TabBarPresenter)
//{
// TabBarPresenter.ShowView(view);
// return;
//}
base.Show(view);
}
}
I still don't understand the purpose of this code as it's making troubles. By removing it, everything works fine. (Code was from the example, to find here: https://github.com/slodge/MvvmCross-Tutorials/blob/0f313e3be66b06c110f587b653b9b0c831fb7164/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Touch/ConferencePresenter.cs)
Generally you use a CustomPresenter for this type of logic - see N=25 in http://mvvmcross.wordpress.com for one example.
Your custom presenter can do things like:
hiding the top-level navigation bar (like on https://github.com/slodge/NPlus1DaysOfMvvmCross/blob/master/N-25-Tabbed/Tabbed.Touch/Setup.cs#L41) - this can be done at different levels
delegating Show requests to navigation controllers sitting within the tab children: like on https://github.com/slodge/NPlus1DaysOfMvvmCross/blob/master/N-25-Tabbed/Tabbed.Touch/Setup.cs#L58
directly manipulating the UIViewController[] array - e.g. something like
public override void Show(IMvxTouchView view)
{
base.Show(view);
if (view is MainView
&& MasterNavigationController.ViewControllers.Length > 1)
{
MasterNavigationController.ViewControllers = new UIViewController[]
{
MasterNavigationController.ViewControllers.Last()
};
}
}
For more on custom presenters, see https://github.com/slodge/MvvmCross/wiki/Customising-using-App-and-Setup#custom-presenters and http://slodge.blogspot.co.uk/2013/06/presenter-roundup.html
This article might be especially useful - http://deapsquatter.blogspot.co.uk/2013/06/custom-presenter-for-uitabbarcontroller.html
It seems like this should be very easy, but I'm missing something. I have a custom Element:
public class PostSummaryElement:StyledMultilineElement,IElementSizing
When the element's accessory is clicked on, I want to push a view onto the stack. I.e. something like this:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (MyParent != null) {
MyParent.PresentViewController(new MyDemoController("Details"),false,null);
}
};
Where MyDemoController's gui is created with monotouch.dialog.
I'm just trying to break up the gui into Views and Controlls, where a control can push a view onto the stack, wiat for something to happen, and then the user navigates back to the previous view wich contains the control.
Any thought?
Thanks.
I'd recommend you not to hardcode behavior in AccessoryTapped method, because the day when you'll want to use that component in another place of your project is very close. And probably in nearest future you'll need some another behavior or for example it will be another project without MyDemoController at all.
So I propose you to create the following property:
public Action accessoryTapped;
in your element and its view, and then modify your AccessoryTapped is that way:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (accessoryTapped != null) {
accessoryTapped();
}
};
So you'll need to create PostSummaryElement objects in following way:
var myElement = new PostSummaryElement() {
accessoryTapped = someFunction,
}
...
void someFunction()
{
NavigationController.PushViewController (new MyDemoController("Details"), true);
}