-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'NativeScript:main' into fix-links
- Loading branch information
Showing
11 changed files
with
447 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
--- | ||
title: Best Practices with Android and NativeScript | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
## Images in Android Drawables | ||
|
||
With the large number of devices available in the world and the numerous different display sizes and resolutions it is generally a good idea to serve images that scale with the device screen. | ||
|
||
On Android images inside the various drawable directories are used to serve the image that scales for the device display. | ||
|
||
Google provides a great resource to learn more about [supporting different pixel densities](https://developer.android.com/training/multiscreen/screendensities). | ||
|
||
In our NativeScript project, you will have the `App_Resources/Android` directory. Everything in here will mirror a standard Android project. Inside the `App_Resources/Android/src/main` directory you should have a `res` directory which contains many `drawable-...` directories. This is where you should place images that have been resized for the different device displays. To read more on this topic you can read [the Android documentation for alternative bitmaps](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp). | ||
|
||
::: tip Note | ||
Any time you edit files inside `App_Resources` it is a best practice to execute `ns clean` which will clean your project build so the next build will be a fresh native build of the application. | ||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
--- | ||
title: Best Practices around hidden, visibility, v-if, ngIf and all those "if" things | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
There are various ways to hide and show view elements on the screen at any given time either dynamically or just per page. | ||
|
||
## From @nativescript/core | ||
|
||
- `visibility: 'hidden' | 'collapse' | 'visible'` | ||
- `hidden: boolean` (new in 8.x+) | ||
|
||
Both are very similar but have subtle differences that are worth noting. | ||
|
||
## From various frontend framework flavors | ||
|
||
- `ngIf`: Angular | ||
- `v-if`: Vue | ||
|
||
Most frontend framework integrations add their own sugar in the form of `v-if` (Vue) and `ngIf` (Angular) to name a few which are also very useful but have unique characteristics that are important to understand. | ||
|
||
## Most expensive view operation | ||
|
||
Direct visual tree manipulation is the most expensive operation you can do generally speaking. More specifically creating new visual nodes, inserting into the tree and removing nodes is expensive. When done on a single render pass (showing a page) or when a user taps a button to see a visual changes is not a big deal. But when done repeatedly in the moment of what a user would expect to be smooth (like scrolling) it can be devastatingly bad. This is because doing so often forces the rendering engine to have to remeasure and adjust the layout which is often visibly janky and not desirable. This is not NativeScript related but rather pure rendering engine on each platform related. It's also a topic that has been talked about at depth with regards to Web DOM performance. Even though NativeScript views are not using the DOM, the same practices apply with regards to achieving optimal view rendering performance. | ||
|
||
## What occurs with `v-if`, `ngIf`, etc.? | ||
|
||
These types of bindings completely destroy a view node when hiding and recreate it when showing. This is perfect for many uses cases but should be avoided on anything that involves scrolling which would cause those bindings to change mid-flight during scroll behavior. Or really any other highly interactive user experience which causes binding changes. | ||
|
||
On the other hand, `hidden` and `visibility` both **do not destroy** the view node. They simply modify it's properties to hide it or show it. The key differences between the two are illustrated here in this sample: | ||
|
||
<img src="/assets/images/hidden-v-vis.gif"> | ||
|
||
The difference in their usage is very subtle: | ||
|
||
- `visibility: 'hidden' | 'collapse' | 'visible'`; | ||
- `hidden: boolean`; | ||
|
||
We can see `visibility` supports 3 modes where `hidden` is a boolean which acts just like `visibility: 'collapse'` would. So why would you ever use `hidden`? It's much simpler binding `[hidden]="condition"` vs. `[visibility]="condition ? 'visible' : 'collapse'"` so purely syntactical sugar. But it's important to note that `hidden: false` and `visibility: 'collapse'` both affect the layout whereas `visibility: 'hidden'` does not! | ||
|
||
Therefore `visibility: 'hidden'` can be superb for various performance dialing cases on recycled rows which you don't want to cause a layout change (bad performance) while a user is scrolling. | ||
|
||
Typically where `ngIf`, `v-if` and the variants don't fit the performance case you are working with, `hidden` or `visibility` will and they can often be the key to unlocking the performance you are after in those situations. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
--- | ||
title: Best Practices | ||
description: Over the years several distinct best practices have emerged when working with NativeScript and we'd like to outline a few here for you in hopes that you can enjoy NativeScripting as much as we do. More importantly though, that your end product gives you all the right feels you expect. | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
### The story of 😢 or 😊 | ||
|
||
We are providing a sample "vanilla" NativeScript app which shows some of the bad vs. good practices for you to run and see for yourself. | ||
Using vanilla NativeScript is a great way to see the raw examples of why these are bad and good practices which are applicable to any frontend framework integration. Each framework integration effectively utilizes these same practices for it's own various cases. | ||
|
||
Clone and see for yourself: https://github.com/NativeScript/examples-best-practices | ||
|
||
### [View bindings](view-bindings.md) | ||
|
||
### [ListViews](listviews.md) | ||
|
||
### [hidden, visibility, v-if, ngIf and all those "if" things](if-things.md) | ||
|
||
### [Rogue timers and intervals](rogue-timers.md) | ||
|
||
### [Various usages of @NativeClass() decorator](native-class.md) | ||
|
||
### [Android tips](android-tips.md) | ||
|
||
### [iOS tips](ios-tips.md) | ||
|
||
### [.ios and .android files or platform conditionals?](platform-file-split-or-not.md) | ||
|
||
### [Optimizing Images](optimizing-images.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
title: Best Practices with iOS and NativeScript | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
## Delegates, delegates, DELEGATES!! | ||
|
||
**Always** retain custom delegate implementations that you use in a your own custom iOS classes. Not doing so can cause your delegate to get garbage collected early and functionality not working as expected. | ||
|
||
For example: | ||
|
||
- **BAD** | ||
|
||
```ts | ||
let applePayController: PKPaymentAuthorizationViewController | ||
|
||
applePayController = PKPaymentAuthorizationViewController.alloc().initWithPaymentRequest( | ||
paymentRequest | ||
) | ||
applePayController.delegate = PKPaymentAuthorizationViewControllerDelegateImpl.initWithOwner( | ||
this | ||
) | ||
``` | ||
|
||
- **GOOD** | ||
|
||
```ts | ||
let applePayController: PKPaymentAuthorizationViewController | ||
let applePayControllerDelegate: PKPaymentAuthorizationViewControllerDelegateImpl | ||
|
||
applePayController = PKPaymentAuthorizationViewController.alloc().initWithPaymentRequest( | ||
paymentRequest | ||
) | ||
applePayControllerDelegate = PKPaymentAuthorizationViewControllerDelegateImpl.initWithOwner( | ||
this | ||
) | ||
applePayController.delegate = applePayControllerDelegate | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
--- | ||
title: Best Practices with ListViews, RadListView, etc. | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
Quite possibly the _most_ important controls available on all mobile devices which have the most impact on perceived app performance. | ||
|
||
ListView, RadListView and really any view component that utilizes some form of row recycling with view templates/components. | ||
|
||
## Bad setup | ||
|
||
Let's first look at a bad way to setup a ListView row template and why: | ||
|
||
```xml | ||
<ListView items="{{ myTitles }}" class="list-group"> | ||
<ListView.itemTemplate> | ||
<StackLayout> | ||
<r:Render when="{{ !showSomethingElse }}"> | ||
<r:Render.template> | ||
<StackLayout backgroundColor="lightblue"> | ||
<Label text="{{ title }}" textWrap="true" class="title" /> | ||
</StackLayout> | ||
</r:Render.template> | ||
</r:Render> | ||
<r:Render when="{{ showSomethingElse }}"> | ||
<r:Render.template> | ||
<GridLayout rows="auto,5,auto,5,auto" backgroundColor="red"> | ||
<Label text="{{ title }}" textWrap="true" class="title" /> | ||
<Label row="2" text="Something Else" textWrap="true" class="title" /> | ||
<Label | ||
row="4" | ||
text="Could show yet anything thing here" | ||
textWrap="true" | ||
class="title" | ||
/> | ||
</GridLayout> | ||
</r:Render.template> | ||
</r:Render> | ||
</StackLayout> | ||
</ListView.itemTemplate> | ||
</ListView> | ||
``` | ||
|
||
This uses a custom `Render` component which **simulates** how various frontend framework integrations actually behave under the hood when `v-if` (Vue) and `ngIf` (Angular) are involved. The problems when using such things in the context of view scrolling and recyclyable rows with ListView controls can be devastating to user experience. | ||
|
||
This is bad because it causes the creation and destruction of view elements while the user scrolls which is not performant at all. | ||
|
||
## Good setup | ||
|
||
```xml | ||
<ListView items="{{ myTitles }}" class="list-group" itemTemplateSelector="{{selectItemTemplate}}"> | ||
<ListView.itemTemplates> | ||
<template key="onelayout"> | ||
<StackLayout backgroundColor="lightblue"> | ||
<Label text="{{ title }}" textWrap="true" class="title" /> | ||
</StackLayout> | ||
</template> | ||
<template key="anotherlayout"> | ||
<GridLayout rows="auto,5,auto,5,auto" backgroundColor="red"> | ||
<Label text="{{ title }}" textWrap="true" class="title" /> | ||
<Label row="2" text="Something Else" textWrap="true" class="title" /> | ||
<Label row="4" text="Could show yet anything thing here" textWrap="true" class="title" /> | ||
</GridLayout> | ||
</template> | ||
</ListView.itemTemplates> | ||
</ListView> | ||
|
||
// Using template selectors | ||
selectItemTemplate(item, index, items) { | ||
return item && item.showSomethingElse ? 'anotherlayout' : 'onelayout'; | ||
} | ||
``` | ||
|
||
Using template selectors is something that ListView and RadListView support and should always be used when needing to do conditional layouts in rows while a user scrolls. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
title: Best Practices with usages of the @NativeClass() decorator | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
The `@NativeClass()` decorator was introduced in NativeScript 7 and offers a simple way to decorate any class intended to extend/customize platform native classes in the JavaScript ESM world. | ||
|
||
There's a couple things to note about it's usage. | ||
|
||
## When used only within a single file | ||
|
||
When using the custom native class within the same file you can define it as follows: | ||
|
||
```ts | ||
@NativeClass() | ||
class CustomClass extends NSObject {} | ||
|
||
const customClassInstance = CustomClass.new() | ||
``` | ||
|
||
This allows the code to be compiled naturally and will work as expected. | ||
|
||
## When needing to define iOS and Android custom native classes in a single file | ||
|
||
You can use setup methods in this case to mitigate any cross compilation issue, for example: | ||
|
||
```ts | ||
let customClass | ||
function setupCustomClass() { | ||
if (global.isAndroid) { | ||
@NativeClass() | ||
class CustomClass extends android.view.View {} | ||
customClass = CustomClass | ||
} else { | ||
@NativeClass() | ||
class CustomClass extends NSObject {} | ||
customClass = CustomClass | ||
} | ||
} | ||
|
||
setupCustomClass() | ||
const customClassInstance = new customClass() // can handle different platform args with ternary if needed | ||
``` | ||
|
||
The `global.isAndroid` conditional will get removed when building the app for iOS so your compiled code is clean and isolated while allowing you to handle in a single file. | ||
|
||
## When exported from a file and used elsewhere | ||
|
||
Since the `@NativeClass()` decorator modifies the JS syntax to work with the NativeScript runtimes you want to ensure the symbol is exported properly when using that outside of the file it's defined. Here's how you can do that: | ||
|
||
```ts | ||
// custom-class.ts | ||
@NativeClass() | ||
class CustomClass extends NSObject {} | ||
|
||
export { CustomClass } | ||
|
||
// usage.ts | ||
import { CustomClass } from './custom-class' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
--- | ||
title: Optimizing Images | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
The importance of optimizing images for mobile devices is critical to your app's performance. This is true no matter what framework or OS your app targets. | ||
|
||
It is even important when building for web and desktop as well but not as critical since those devices typically have much greater memory and CPU to operate with, however you should still optimize your images regardless of any target platform you are distributing applications for. | ||
|
||
## Sizing | ||
|
||
Devices today have incredible displays with high-resolution screens. With the high-resolution screens we need to provide our users with high-resolution images which will increase file size. | ||
|
||
One of the best options is to test your image in various devices and screen sizes. Once you have the image you can include the image in your application and see how it looks. Check the size of the image. If it's a high-resolution image and its size is `1024x1024` and you are displaying a small thumbnail or icon in your app. You probably do not need it to be that size and you can scale it down by resizing the image on your computer. Try different sizes to get the best result while keeping the image looking as sharp as possible. | ||
|
||
If you want to take it a step further and provide a better end user experience typically on Android and iOS you would package different sized images in your application. | ||
|
||
In NativeScript, these images will be located in your `App_Resources/<platform>` directory for your application. The directory structure inside `App_Resources` mirrors that of a native Android and iOS project. These files are used during the native build of your application. | ||
|
||
For more on optimizing images for Android using the Android drawable directories, see the [Android Tips](android-tips.md#images-in-android-drawables) section of the docs. | ||
|
||
## Compression | ||
|
||
There are numerous compression methods for manipulating images. For most images you are working with you can choose some compression option and end up hardly noticing a difference in the end result, however, you can significantly reduce the file size of the image. Which in turn reduces the overhead of using that application in your application by consuming less memory on the device. If you have ever worked with a lot of images on Android, you may be familiar with [`OOM Exception`](https://developer.android.com/reference/java/lang/OutOfMemoryError) which most Android developers can tell you this happens when you are displaying a lot of images and forget to reduce their size or optimize your layouts. | ||
|
||
One example of image compression is [lossy compression for PNG files](https://en.wikipedia.org/wiki/Lossy_compression). | ||
|
||
A great resource at [tinypng.com](https://tinypng.com/) is available to optimize images where you can quickly see how much smaller your image file sizes can be when compressed. | ||
|
||
The website has the following example: | ||
|
||
![TinyPng Example](../assets/images/optimization/tinypng-example.png?raw=true 'TinyPng Example') | ||
|
||
With one example you can see how easily you can reduce the file size significantly, which will improve your app's performance when it is displaying images. Another benefit of compressing your images is smaller installables for the end user to download for the Apple App Store and Google Play Store. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
title: Best Practices with when to split up .ios and .android files vs. using platform conditionals | ||
contributors: | ||
- NathanWalker | ||
- rigor789 | ||
--- | ||
|
||
## Using platform conditionals vs splitting into platform files? | ||
|
||
This question is asked a lot in terms of best practices. | ||
|
||
Using "platform conditionals": | ||
|
||
```ts | ||
if (isIOS) { | ||
// handle ios | ||
} else if (isAndroid) { | ||
// handle android | ||
} | ||
``` | ||
|
||
Using platform files: | ||
|
||
- `file.ios.ts` | ||
- `file.android.ts` | ||
|
||
The advent of tree shaking and webpack builds does away with quite a bit of worry in this area however there's a few things to consider here. | ||
|
||
## Conditional with tree shaking | ||
|
||
When speaking of tree shaking ever since NativeScript 7, you've been able to use `global.isAndroid` or `global.isIOS` and anytime those are used as conditional splits in your code, only the applicable code for the platform that's being built would actually end up in your compiled code alleviating a lot of concern here. | ||
|
||
## Future maintenance | ||
|
||
The other aspect comes down to maintenance and clarity over time. | ||
|
||
When dealing with just a few lines of code for either platform, using a conditional is certainly highly maintainable and worry free. | ||
|
||
General advice is if you have more than 10 or so lines of code for a particular platform use case then splitting into separate `.ios` or `.android` files can certainly help make that code more maintainable and easier to scale over time. | ||
|
||
Bottom line is to not fret over these details too much. Conditionals can be tweaked with webpack to exclude certain conditions where you want to dial your distribution bundle in production to only include code applicable for the runtime platform. Outside of that your own preference is just fine. |
Oops, something went wrong.