18

Note: When I talk about App Backend I do not mean Server backend. The App backend is part of the app. It is the non-ui part of the app.

I have a code design question. Using j2objc Java is used as backend of an iOS app, where the frontend is still Objective-C.

Which part (front end or backend) should control navigation in this model?

Consider the following. The initial ViewController has been loaded. The user tapped on a button. Here are two possible cases:

  1. the front end receives the gesture and opens the requested ViewController

  2. the front end receives the gesture and reports the action to the Java backend. The Java backend decides which page to open next and tells the front end which ViewController is to be revealed.

To me the second solution seems to make more sense in turns of code separation. But there is one problem which occurred to me. Assume you have the following ViewController structure in your app:

  • Startpage : UIViewController Main: UINavigationViewController
  • > Main-TabPage1: UIPageViewController
  • > Main-TabPage2: UIPageViewController
  • > Main-TabPage3: UIPageViewController Settings: UIViewController

If you navigate on the to level of the app it is simple. You just tell the front end open Startpage, Main or Settings. But, what of the use tapped a button to go to Main > Main-TabPage3 from a Startpage or from Main > Main-TabPage1. You have to do the following:

  • in case you are on Startpage: you have to tell the Front end that it should review Main and then Main TabPage3.
  • in case you are on Main > Main-TabPage1: you have to tell the Front end that it should just reveal Main-TabPage3.

You see that event for such a simple page scheme revealing a ViewController fron the Java back end seems to have a lot of cases to consider.

Is this a valid way of revealing views from a Java back end or do you see any better way of doing it?

Michael
  • 32,527
  • 49
  • 210
  • 370

2 Answers2

4

A good practice is indeed to split your application in two parts:

The domain: Contains all the service classes for remote communications (that is, if you need to hit a distant backend), and every classes and models related to your business logic. This part is the one that use j2objc, and you want it to be unconnected to your UI.

The UI: All the ViewControllers and Views belongs here.

Why are they distinct from each other?

Once you are set with all your business logic, you should be good to generate your code with j2objc and leave it practically untouched. Your UI, on the other end, may change a lot in the lifespan of your application (remember how iOS 7 forced every developer to refresh the visual of their applications). You want to keep updates in the domain or the UI from impacting the other part.

How do they work together?

Following an action (i.e. a tap on a button), you may ask the domain if some prerequisites are met (i.e is it a new user?), and then you instantiate a ViewController for display. The UI may give data to the domain or ask for some, but it is its responsibility to instantiate Views/ViewControllers.

Complex routing

In applications where I need to be able to navigate somewhere based on a remote push notification, or based on some complex logic, I tend to let an object (called a "router") handle all of this. It still reside in the UI part, and you pass it the domain objects required to make the decisions. It then returns a navigation object depicting a navigation stack (it could be an object handling a string similar to an URL) that you could give recursively to you viewControllers.

Let say your "router" object returns a navigation object "firstVC/secondVC/thirdVC" to the AppDelegate. The AppDelegate could call a dequeue method that returns "firstVC", and based on this instantiate a "firstViewController" object to add in a UINavigationController's stack. It then pass the "router" object to this newly instantiated ViewController, who also call the dequeue method to receive "secondVC". Based on this, "firstViewController" would instantiate a "secondViewController" object to add to the UINavigationController's stack. It would then pass the same "router" object to that "secondViewController" object, and that one would again call the dequeue method to get "thirdVC" and instantiate the appropriate ViewController.

That way, you build your navigation stack by letting every ViewController instantiate another ViewController it knows about (in my previous example, "firstViewController" knows about "secondViewController" because its view has a button allowing the user to reach "secondViewController"'s view, but doesn't know about "thirdViewController").

You could also have your AppDelegate build the whole navigation stack, but since your "firstViewController" already have "secondViewController" header imported for the button to work, it is better to let him do the job associated with that ViewController, etc.

Working sample

Here is a demo project I made to illustrate what I just exposed.

Classes contained in the "Domain" folder could be your classes generated with j2objc. The buildNavigationStack method in AppDelegate build a functional navigation stack with a 'Router' object. I made this project skeleton simple while showcasing a few patterns, like the factories for a centralized & organized class instantiations, and the viewData that encapsulate a model object and output data ready for display. It could be improved with a few protocols to make things a little more abstract, but I tried to keep it simple since the for the sake of the presentation.

Rufel
  • 2,630
  • 17
  • 15
  • Could you provide some code examples for your description? – Michael Apr 21 '15 at 11:02
  • @Rufel The Router should be part of the Domain because when you will use your code for a platform like Android you will need that too. When you dequeue the route ``secondVC`` how do you know whether you have to put the secondViewController on the UINavigationController's stack or for example as a modal view on firstViewController? –  Apr 24 '15 at 00:22
  • @Rufel What if you want to perform a back navigation from ``third``? You would capture the back button tap and send it to the Domain. Then the domain tells you the new route is "/first/second" but then you start all over again from the first instead of going backward? – Michael Apr 24 '15 at 00:24
  • @stephan1001: I still believe the 'Router' belongs to the UI, because the views architecture is platform dependant (you could use the 'Domain' to support a web-based application, and in this case the flow would be totally different). The 'Domain gives all the knowledge, the 'Router' just build a straight if/switch statement around that. If you decide to move your 'edit profile' view access from the user's view to the menu, it shouldn't impact your 'Domain' but only the 'UI', since it is only a matter or organizing the views architecture. You may even want to change it only on a specific OS. – Rufel Apr 24 '15 at 18:23
  • @confile: I don't think the "back" action should be handled by the 'Router'. Just as you don't have to care about the user tapping the back button on a browser, the back button on a UI is part of the navigation "concept" (if you move from step to step, you can also go back to the previous step). On iOS you even have a "back" swipe gesture that pop the current VC from the stack without asking what to show next. However, if the user interact on a push notification that requires to backtrack, I think it is fair to rebuild the navigation stack since it could lead you anywhere else in the app. – Rufel Apr 24 '15 at 18:38
  • @Rufel Rebuilding the navigation stack every time is a bad idea. It might take a while to load intermediate Data on the way to the current page. And also the navigation might look strange to the user. – Michael Apr 24 '15 at 19:22
  • @confile I did not mean to rebuild it every time: only if the user took action on a push notification that require the application to backtrack a few steps. The "back" button is already handled successfully by the OS. If your application is very data intensive, it would not be difficult to adapt the ```buildNavigationStack``` & ```navigateWithRouter:``` method to check the current navigation stack, pass the 'Router' object to the VC if the right one is already instantiated, and use ```popToViewController:animated``` when the flow diverge. This way you'd keep VC's that need to stay in place. – Rufel Apr 24 '15 at 19:37
  • @stephan1001 Once a VC dequeued a route and determined which VC to display next, it handles it as if the user just interacted in its UI to display it: it knows if it is modal or pushed in the navigation controller. In my sample project, have a look at the ```navigateToCredentialsWithRouter:``` method of the "UserProfileViewController". It's the same method called by the ```changeCredentialsButtonTapped:``` following the tap on a button, and that VC knows that "UserCredentialsViewController" is made to be shown in modal (in this case). – Rufel Apr 24 '15 at 19:49
  • @Rufel I do not think this is a good way of doing it. Forget about push notification its content can be passed to the domain and handled there appropriately. – Michael Apr 24 '15 at 20:06
  • @Rufel Still I do not see the flexibility of your system there seems to be a lot of special cases when to do what and it is hard to extend. I am looking for something much more flexible. – Michael Apr 24 '15 at 20:07
  • @Rufel This is still no good answer. It does not solve the problem. –  Apr 26 '15 at 08:49
  • @stephan1001 you may want to be more concise with your question then, because I explained why I would separate views from the domain (you asked which part should control the domain). I really advise against a domain giving view class names or instances. – Rufel Apr 26 '15 at 14:26
1

I think a good way is to send local notification from the App Backend and let the view respond to it and display the correct ViewController with tweak that if the view is not visible it should do the action in view will appear

for example in the case you have explained the code will be as the following:

From backend:

NSDictionary *userInfo = @{@"view": @"TabPage3"};
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:@"ShowView" object:nil userInfo:userInfo];`

inside MainView

- (void)viewDidLoad {
    [self viewDidLoad];
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(showView:) name:@"ShowView" object:nil];
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if(self.receivedNotification != nil){
        [self showView:self.receivedNotification];
        self.receivedNotification = nil;
    }
}
- (void)showView:(NSNotification*)notification {
    if (viewController.isViewLoaded && viewController.view.window) {
        // viewController is visible
        if ([notification.userInfo valueForKey:@"TabPage3"]){
            [self.tabBarController setSelectedIndex:3];
        }
    } else {
        self.receivedNotification = notification;
    }
}

inside StartPage

- (void)viewDidLoad {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(showView:) name:@"ShowView" object:nil];
}
- (void)showView:(NSNotification*)notification {
    if ([notification.userInfo valueForKey:@"TabPage3"]){
        [self.NavigationController pushViewController:mainViewController animated:YES];
    }
}
Mahmoud Adam
  • 5,772
  • 5
  • 41
  • 62
  • ``so you need to configure all cases that could happen and handle them in all views`` this is very general statement. Your answer is very badly written and hard to follow. Could you please make a more clear statement. –  Apr 24 '15 at 17:02
  • @stephan1001 check the modified answer – Mahmoud Adam Apr 24 '15 at 17:30
  • What If the ViewController has not been created when you send the notification? Assume you are on the user page and want to navigate to the DetailsPage and the DetailsPage has not been created before then nothing happens. How would you handle that case? –  May 18 '15 at 16:16
  • This can be handled in the parent ViewController that will check if this ViewController is not yet created (displayed) then show it and ask it to update itself by passing the needed information if necessary – Mahmoud Adam May 18 '15 at 16:59
  • How will you do this check? –  May 18 '15 at 17:09
  • here is how to check if a Viewcontroller is visible http://stackoverflow.com/a/2777460/1190861, if the parent is visible so it will push the detail and ask it to do the needed logic, and if the detail is visible it will directly do the needed logic, btw this is already in the code snipped in the solution – Mahmoud Adam May 18 '15 at 19:10