Cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Multi-screen App Template - Ionic Views v1.1

If you wanted to have the multi-screen application boilerplate having SVG split to several .gui files, you now have it. Updated Ionic Views UI micro-framework.

 

Same for jQuery-style $( '#id1 #id2' ) selectors.

 

Same for hierarchical subviews. View now comes with an energy-saving bonus: render is automatically suppressed when the screen is off (and app render is forced when the screen goes on). So, don't bother with that optimization. It just works.

Special thanks to @kmpm who helped to test and debug the thingy. Btw its size is about 100 lines of code, don't be afraid to look inside.

Don't forget to put a star if you like it.

Best Answer
24 REPLIES 24

Nice multiscreen boilerplate.

 

Now I need to work out where to place the code to periodically read sensor data even if the screen is off...

 

Quite happy to share the project once it is in a stable state ....

Best Answer
0 Votes

You do it in the separate class, which should be instantiated in application’s class onMount().

Best Answer
0 Votes

Look how it’s done in Screen1. It subscribes to the clock tick, and the handler is called even when the screen is off. View.render(), however, is no-op in this case and view.onRender() won't be called.

You should not do any data processing in onRender() method itself. Just update the UI here. In general, the view pattern resembles BackboneJS View and should be treated accordingly.

Best Answer
0 Votes

@gapertonthanks for your replies:

 

I have added sensorReadings.js in app:

export class sensorClass {
  myVar=0;
  myTimer=0;

  start() {
    this.myTimer=setInterval(this.increment(),1000);
    console.log("Start");
  }
  
  increment() {
    this.myVar++;
    console.log(this.myVar);
  }
  
  
  stop() {
    clearInterval(this.myTimer);
    console.log("Stop");
  }
}

and have the following in index.js

import document from "document";
import { Application } from './view'
import { Screen1 } from './screen1'
import { Screen2 } from './screen2'
import { sensorClass } from './sensorReader'

class MultiScreenApp extends Application {
    screen1 = new Screen1();
    screen2 = new Screen2();

    mySensorReadings = new sensorClass();

    // Called once on application's start...
    onMount(){
        // Set initial screen.
        // Same as Application.switchTo( 'screen1' ), which might be used to switch screen from anywhere.
        this.screen = this.screen2;
        this.mySensorReadings.start();

        document.onkeypress = this.onKeyPress;
    }

    onUnmount() {
      this.mySensorReadings.stop();
    }

    // Event handler, must be pinned down to the class to preserve `this`.
    onKeyPress = ({ key }) => {
        if( key === 'down' ){
            // Just switch between two screens we have.
            Application.switchTo( this.screen === this.screen1 ? 'screen2' : 'screen1' );
        }
    }   
}

// Create and start the application.
MultiScreenApp.start();

As I am intending to log data on a periodic basis, I am assuming the best place to do this is from the main application, so that portions from the same base of data can be used across different screens.

 

So build the class that will "kick off" the sensor event subscriptions, and record the data within the class. Run a secondary timer to bring together the readings from the various sensors, make calculations (distance from GPS, average HR & cadence i.e. all data processing) and log to a file for later analysis (once messaging is reliable). Sensor events can be unsubscribed in unmount if not already stopped.

 

In the above code, I do indeed get the console log that the start function has fired. And that is it. No periodic messages. No message prior onunmount. What have I done wrong (or broken)?

 

Is my thought process broken??

Best Answer
0 Votes

Is my thought process broken??

 

Nah. Just the small bug in your code. In this line:

 

this.myTimer=setInterval(this.increment(),1000);

It should be:

this.myTimer=setInterval( () => this.increment(), 1000 );

 

Best Answer

Also, if your intention is to use the single instance of the SensorClass in all the views, it might be better to export it right from the module. Like this:

 

class SensorClass {
constructor(){
// ...
this.start();
}

// ... }

export default new SensorClass();

Then, it can be imported from anywhere like this.

import sensor from './sensor'

The different question is how you will update our UI in case of changes. The simplest way I would recommend to start with is having the single onChange callback in your sensor class and calling the Application.render. Thus, whatever screen is active it will be updated on reading changes, and you don't need to manage multiple subscribers in sensor class.

onMount(){
    this.screen = this.screen2;
document.onkeypress = this.onKeyPress;
sensor.onChange = () => this.render();
}



Best Answer

Understood. And works like a charm.

 

I will go ahead and re-build my app using your boilerplate and upload to github. Expect an incoming post by the close of the weekend. 🙂

Best Answer

Hello again @gaperton:

 

I am now just trying to work out how to reference an Application method from a screen class.

 

I have created an "Are you sure you want to exit?" screen, which of course switches into place when required. I have yes and no buttons.

 

Whilst I can use an me.exit() from  within the exit screen, I don't believe that is the correct place, and should reside at the Application class level.

 

I did try to ensure that the onUnmount method fires by putting a console.log in place - but it didn't produce any output. Could you double check the onUnmount method does function for you?

 

Thanks

Rob

Best Answer
0 Votes

> I am now just trying to work out how to reference an Application method from a screen class.

import { Application } from './view'
...
Application.instance.anyMethod(); // Bingo!

// To switch the screen from anywere there's the dedicated static:
Application.switchTo( 'nameOfTheAppInstanceMemberHoldingView' );

 Everything is taken care of ;).

Best Answer

> I did try to ensure that the onUnmount method fires by putting a console.log in place - but it didn't produce any output. Could you double check the onUnmount method does function for you?

At the time it never works for an Application instance itself, but it should work for the application's screen which is being replaced with another one as well as for any of its subviews.

Probably, it's a good idea to add Application.exit() method which would unmount the screen and call its own onUnmount() hook.

Best Answer

Just uploaded to github a far from finished application.

 

Quite a bit of finessing to do, and all of the styling.

 

It does give a start to using the Ionic Views template. Hopefully it will help a few people. It may even give the opportunity of the more seasoned developers to contribute/advise if so wished.

 

https://github.com/non-runner-rob/runmaster

 

RunMaster is a personal only project, so I won't be releasing it. Got lots planned for it moving forward. 

Best Answer

@SunsetRunner Somehow you uploaded the result of transpiration. Check your app folder, there’s just a single index.js file.

 

Try “Export” option in FitBit studio.

Best Answer
0 Votes

@gaperton Fixed

 

Not sure what I did (not used GitHub before)

 

The correct files are now uploaded. (Note to self: Check and Double check what you are uploading).

Best Answer

Small note regarding your views code. Let's take this as an example.

https://github.com/non-runner-rob/runmaster/blob/master/app/screen-entry.js

Here it would be good to do either of two things:

1) Merge view and element group together. I.e. to cache elements in the view, and update them directly in onRender(). When you have just one element group it means you don't really need it.
2) Split your elements group into several logical groups. Like this:

class Times {
duration = $ ( '#duration' );
startTime = $ ( '#startTime' );
endTime = $ ( '#endTime' );
currentTime = $ ( '#currentTime' );

// UI update method(s). Can have any name, it's just the pattern.
// Element groups have no lifecycle hooks, thus all the data required for UI update
// must be passed as arguments.
render({ pduration, pstartTime, pendTime, pcurrentTime }){
this.duration.text = parseInt(pduration/1000);
this.startTime.text = pstartTime;
this.endTime.text = pendTime;
this.currentTime.text = pcurrentTime;
}
}

Note that I grouped arguments in render to an object. See { .. } brackets? So, you will render it like this:

onRender(){
  this.times.render( runMaster ){
// render other elements groups... }

Which is much better, isn't it? That's how they meant to be used. Logically group UI elements so render code won't look so messy.

Best Answer
0 Votes

@gaperton

 

As said above, lots of finessing to do. Appreciate you input.

 

Only threw in all the stats onto one screen to confirm they are as they should be. Will then split them out accordingly into specific screen displays. Will also provide methods in RunMaster to provide formatted data rather than at each point the data is consumed.

 

Still learning this new fangled class thingamybobwhatsit Smiley Very Happy

 

Ionic Views does make things so much more straightforward for me.

Best Answer
0 Votes

Will also provide methods in RunMaster to provide formatted data rather than at each point the data is consumed.

This part is questionable. Not that it would break your app or anything like that, it's rather about the general UI apps architecture. I will explain.

An overall point of good architecture is to control the complexity. The "complex" can be defined as "big and messy", so what you do to fight the complexity is splitting something "big" to the set of loosely connected smaller parts.

The first step is to use the "layers" pattern. There are at least two "layers" naturally present in your app - "view layer", and "data layer". The data layer (runMaster) is on the bottom, which means that it should have no knowledge of the upper "view" layer. It means that the data layer should not know about formatting. Formatting is dependent on data representation in the UI, which by definition is the "view" layer job.

Why it's helpful? Imagine that someone needs to make a change to look and feel of your app. If your data layer is clearly separated, such a person can safely ignore it concentrating on the view layer only and making the change which is local to the single view. That's the point - he doesn't need to understand your system as a whole to make his change, he can concentrate on the small isolated part. The same thing is right for you as well, you can forget about irrelevant parts of your app when you're working on some particular task.

Then, if your views become big/messy, you split them down into subviews/element groups as well. That's how it works. 

Best Answer

The more I work with Ionic Views, the more I like it. 

 

As I add more screens into RunMaster, the more difference it makes.

 

One of the things I am currently trying to work out, is how to have a common gauge across a selected number of data views.

 

Let me explain the need:

 

Creating a running app, one of the things important to me is being able to glance how well I am doing against my pace and average heart rate targets, regardless of what unit of metric I have onscreen at that time.

 

Rather than have that gauge in each of the views, I want to share it so one common design and code base so to speak.

 

Whilst I can put a visible design element with id into the index.gui, I am having difficulty referencing it. So at this stage unsure if a) it is wise to follow this approach b) if there is a better way.

Best Answer
0 Votes

> Rather than have that gauge in each of the views, I want to share it so one common design and code base so to speak.

 

If this widget is positioned on the same place in every screen, an easiest way of doing it is:

1) Include its GUI at the root level once.

2) Define the widget's logic as the separate View.

3) Insert an instance of this View to every screen referencing it. ( myWidget = new MyWidgetView() )

3) In you screen's onMount(), do this.insert( this.myWidget ).

 

That's it. If the position is different, you'll need to use Template Symbols.

Best Answer

When implementing it I got the following situations, do you have any idea how you would handle these cases?
- rendering animating components, for instance a spinner. I moved it to render, but now it restarts every time render is called, even when the loading state does not change.

- back button in views (to go to previous screen)
- using combo buttons in screens

- passing information to new view on switchTo

Best Answer
0 Votes