Reusable and recyclable NativeScript views

Ouverte
#7,469 1 commentaire 7 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Évaluation

Difficulté
5/5
Temps estimé
Plus d'une semaine
Accessibilité débutants
25/100
Type d'issue
Fonctionnalité
Clarté
À clarifier
Activité
À l'abandon
Stack technique
android, ios, typescript
Domaine
mobile-dev

Piste de recherche

Commencez par lire le comportement de suppression des native views introduit dans NativeScript PR 3912 et reproduisez les cas de detach/attach dans les playgrounds TypeScript et Angular liés. Le travail serait terminé lorsqu’un cycle de vie défini pour les vues réutilisables préserverait les native views lors du detach et permettrait de les attacher à un autre parent sans les recréer.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

Is your feature request related to a problem? Please describe.
Removing a NativeScript view from the visualtree results in all native views being disposed on Android. On iOS, some native views are not disposed, but their delegates are. This behavior seems to come from https://github.com/NativeScript/NativeScript/pull/3912 but I haven't found an explanation for why it is that way.

Since the view is destroyed, there is no way to do, for example:

const parent1 = stack1;
const parent2 = stack2;
const st  = new StackLayout();
const bt = new Button()
st.addChild(bt);
parent1.addChild(st);
// st.android != null
parent1.removeChild(st);
// st.android == null
parent2.addChild(st);
// st.android != null

Without the view being destroyed and created again.

This also makes hard to use some plugins like nativescript-popup. We've tried creating an angular template and passing the NS views to the plugin, but once we detached the view from it's parent, the native views became null and the popup was empty. Our workaround was to manually use the native views that were created and set view.parent = null otherwise the app would crash when closed.

Describe the solution you'd like
NativeScript views could be reusable:

const parent1 = stack1;
const parent2 = stack2;
const st  = new StackLayout();
st.reusable = true;
const bt = new Button()
st.addChild(bt);
parent1.addChild(st);
const native = st.android;
parent1.removeChild(st);
native === st.android // true
parent2.addChild(st);
native === st.android // true

This is different from https://github.com/NativeScript/NativeScript/issues/4189, since we're not just reusing the native views, but the whole NS view, so no properties have to be reset. Maybe an additional method (like view.inflate()) could be provided to inflate views without the need to attach to the "dom".

Additional context
I've only used core and angular flavors, so I'll contextualize it using them:

Angular/Vue/React/etc flavors of nativescript would create all their views as reusable. The angular renderer has destroyNode method that is unimplemented by nativescript-angular because the core already destroys the view when it's detached. This method is called when the view is not being used anymore, so the view can be safely detached and reused. I'm sure the other frameworks have similar approaches.

This necessity came up when I was starting to develop a recyclerview plugin with shared pools (which is possible natively, no need for this PR) and general use of nativescript-popup and I stumbled upon my views being destroyed whenever I tried to detach them.

Here's a playground of the current behavior: https://play.nativescript.org/?template=play-tsc&id=eorFFE&v=6

Here's what happens when we try detaching and attaching: https://play.nativescript.org/?template=play-ng&id=mXwdnl

Ideally, "create native view" should only output once for each label created by "Create Views" and none for "Move Views".

Partially inspired by: https://hackernoon.com/react-native-listview-performance-revisited-recycling-without-the-bridge-c4f62d18c7dd?gi=87e16d79933e

Use cases
Custom reusable heavy views/view tree in the core

Allows you to reuse a view or view tree anywhere. Examples: footer that is used in almost every screen only needs to instanced once. Same view pool for multiple ListViews. A conditional view that may be added or not to a ListView item (the current best practice is instantiate all views and set the visibility property, or using multiple templates, potentially increasing memory usage).

// viewrecyler.ts
export class ViewRecycler {
  viewPool = [];
  availableViews = [];
  prefetch = 5;

  constructor() {
    for (let i = 0; i < this.prefetch; i++) {
      this.generateReusableView();
    }
  }

  generateReusableView() {
    const view = new HeavyView(); // HeavyView could be a whole view tree
    view.reusable = true;
    view.inflate(); // force creation, we're prefetching.
    this.viewPool.push(view);
    this.availableViews.push(view);
    return view;
  }

  getView() {
    if (this.availableViews.length === 0) {
      this.generateReusableView(); // if none available, generate a new one
    }
    return this.availableViews.pop();
  }

  storeView(detachedView: HeavyView) {
    this.availableViews.push(detachedView);
  }
}

export const viewRecycler = new ViewRecycler();

// page.ts

let recyclableView;
function navigatedTo() {
  const container = getViewById(page, "viewcontainer");
  recyclableView = viewRecycler.getView();
  container.addChild(recyclableView);
}

function navigatingFrom() {
  if (recyclableView) {
    recyclableView.parent.removeChild(recyclableView);
    viewRecycler.storeView(recyclableView);
    recyclableView = null;
  }
}

It's up to the developer to set/reset view properties. In the example above, all properties must be set on recyclableView = viewRecycler.getView();, or reset on viewRecycler.storeView(recyclableView);. When too many properties are being reset each time (defeating the purpose of recycling), it's recommended to add a new view pool.

ngFor/Repeater

What could be accomplished with this:

<Button *ngFor="let item of item;"></Button>

This is already possible, but every time you swap something, angular uses detach and insert, meaning you get lag. Workaround: use trackBy using index.

<Button *ngReusableFor="let item of items; uniqueKey:'myForKey'"></Button>
<Button *ngReusableFor="let item of items2; uniqueKey:'myForKey'"></Button>

ngReusableFor would have a service that stores views in myForKey. No matter how many items you add/remove from items and item2, the same views would be reused in both of them. caution: the same template has to be used for both fors.

Global templates

My favorite use case.

<GlobalTemplates>
  <ng-template templateKey="reusablecard" let-item="item">
    <CardView>
      <StackLayout>
          <Label text="item.title"></Label>
          <Label text="item.content"></Label>
      </StackLayout>
    </CardView>
  </ng-template>
</GlobalTemplates>

...
<!-- use inside a listview, ngfor, or any html -->
<ReusableTemplate key="reusablecard" context="item"></ReusableTemplate>

Every time you'd use that template, it'd be a recycled instance from somewhere in the app. You could have horizontal listviews inside a vertical listview and they all share the same pool of recycled components. You could also configure some prefetching (create 5 cards when the app starts and just use them over and over).


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

Langage dominant
TypeScript
Étoiles
25.7k
Forks
1.7k
Merge moyen
1 j 5 h
PR mergées (30 j)
35

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de NativeScript/NativeScript

Toutes les issues de NativeScript/NativeScript

Issues similaires

Plus d'issues TypeScript

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.