Skip to content

Crie seu primeiro app ​

Construa o Pocket notes: busque em uma lista pequena, abra uma nota, edite em um modal nativo e receba o texto salvo. Um leave guard protege o rascunho. O Back retorna à mesma lista, com a busca preservada.

O tutorial usa rotas explícitas e APIs públicas. Pressupõe conhecimento básico de componentes Vue e um projeto NativeScript-Vue preparado. As notas são fictícias e ficam em memória; o exemplo não implementa persistência, autenticação ou backend.

Pocket notes em simulador iOS 26: busca preservada, editor modal nativo, cancelamento recusado e resultado salvo. O mesmo código do tutorial executa no demo.

1. Crie os arquivos ​

Depois de instalar o router, coloque estes sete arquivos completos no diretório src do app. Substitua a entrada e a raiz do starter pelos arquivos abaixo; mantenha seus recursos nativos e configuração de build. O código é incorporado diretamente da fonte executável do tutorial, então todos os idiomas usam a mesma implementação. Os textos da interface do exemplo estão em inglês.

text
src/
  app.ts
  App.vue
  router.ts
  notes.ts
  pages/
    Home.vue
    Note.vue
    Edit.vue

O RouterView da raiz cria um Frame nativo. Cada componente de rota renderiza uma Page. Não envolva esse RouterView em outro Frame.

src/router.ts — / contém a lista, :id seleciona uma nota e modal: true dá ao editor seu próprio Frame. props: true passa os params da rota para a página.

ts
import { createRouter } from 'vue-router-ns'

export const router = createRouter({
  routes: [
    { path: '/', component: () => import('./pages/Home.vue') },
    {
      path: '/notes/:id',
      component: () => import('./pages/Note.vue'),
      props: true,
    },
    {
      path: '/notes/:id/edit',
      component: () => import('./pages/Edit.vue'),
      props: true,
      modal: true,
    },
  ],
})

src/app.ts — instale o router antes de iniciar o app.

ts
import { createApp } from 'nativescript-vue'
import App from './App.vue'
import { router } from './router'

createApp(App).use(router).start()

src/App.vue — mantenha o container de navegação montado.

vue
<template>
  <RouterView />
</template>

2. Adicione os dados e a lista ​

src/notes.ts — dados compartilhados pela lista, detalhe e editor. Salvar altera esse array reativo; não grava em disco.

ts
import { reactive } from 'nativescript-vue'

export const notes = reactive([
  { id: 'coast', title: 'Along the coast', body: 'A quiet path beside the sea.' },
  { id: 'forest', title: 'Into the forest', body: 'Follow the shade and slow down.' },
])

src/pages/Home.vue — query pertence a esta página. Abrir uma nota cobre Home; não destrói a página nem sua busca.

vue
<script setup lang="ts">
import { computed, ref } from 'nativescript-vue'
import { useRouter } from 'vue-router-ns'
import { notes } from '../notes'

const router = useRouter()
const query = ref('')
const filtered = computed(() =>
  notes.filter((note) => note.title.toLowerCase().includes(query.value.toLowerCase())),
)
</script>

<template>
  <Page>
    <ActionBar title="Pocket notes" />
    <StackLayout padding="24">
      <Label text="Your first native flow" fontSize="26" textWrap="true" />
      <TextField
        v-model="query"
        hint="Find a note"
        accessibilityLabel="Find a note"
        marginTop="16"
      />
      <Button
        v-for="note in filtered"
        :key="note.id"
        :text="note.title"
        @tap="router.push(`/notes/${note.id}`)"
      />
      <Label v-if="!filtered.length" text="No notes found." />
    </StackLayout>
  </Page>
</template>

A lista com dois itens usa Buttons nativos comuns. Em um catálogo grande, use uma lista virtualizada. O TextField é uma busca simples para começar; busca nativa no header é uma evolução posterior.

3. Abra o detalhe e espere um resultado ​

src/pages/Note.vue — a prop id pertence àquela entrada da rota. pushForResult<string> espera pelo editor; fechar sem resultado devolve undefined. A verificação explícita também permite salvar uma string vazia intencionalmente.

vue
<script setup lang="ts">
import { useRouter } from 'vue-router-ns'
import { notes } from '../notes'

const props = defineProps<{ id: string }>()
const router = useRouter()
const note = notes.find((item) => item.id === props.id)

async function edit() {
  const result = await router.pushForResult<string>(`/notes/${props.id}/edit`)
  if (note && result !== undefined) note.body = result
}
</script>

<template>
  <Page>
    <ActionBar :title="note?.title ?? 'Note not found'">
      <NavigationButton
        text="Back"
        android.systemIcon="ic_menu_back"
        @tap="router.back()"
      />
    </ActionBar>
    <StackLayout v-if="note" padding="24">
      <Label :text="note.body" fontSize="22" textWrap="true" marginBottom="24" />
      <Button text="Edit in a modal" @tap="edit" />
    </StackLayout>
    <Label v-else text="This note does not exist." margin="24" />
  </Page>
</template>

O detalhe de origem continua montado enquanto o modal está aberto. O tipo genérico descreve o contrato entre essas duas páginas; não valida dados arbitrários em runtime.

4. Proteja o rascunho e salve ​

src/pages/Edit.vue — o rascunho é local ao editor. O leave guard condicional pergunta apenas enquanto o texto difere da nota salva. Salvar permite a saída temporariamente e devolve o rascunho por back({ result }).

vue
<script setup lang="ts">
import { computed, ref } from 'nativescript-vue'
import { Dialogs } from '@nativescript/core'
import { onBeforeRouteLeave, useRouter } from 'vue-router-ns'
import { notes } from '../notes'

const props = defineProps<{ id: string }>()
const router = useRouter()
const note = notes.find((item) => item.id === props.id)
const draft = ref(note?.body ?? '')
const saving = ref(false)
const dirty = computed(() => !!note && !saving.value && draft.value !== note.body)

onBeforeRouteLeave(
  () =>
    Dialogs.confirm({
      title: 'Discard changes?',
      message: 'Your unsaved text will be lost.',
      okButtonText: 'Discard',
      cancelButtonText: 'Keep editing',
    }),
  { when: dirty },
)

async function save() {
  if (!note) return
  saving.value = true
  try {
    await router.back({ result: draft.value })
  } finally {
    saving.value = false
  }
}
</script>

<template>
  <Page>
    <ActionBar title="Edit note">
      <ActionItem text="Cancel" ios.position="left" @tap="router.back()" />
    </ActionBar>
    <StackLayout padding="24">
      <TextView
        v-model="draft"
        accessibilityLabel="Note draft"
        automationText="Note draft"
        height="180"
        fontSize="20"
      />
      <Label :text="dirty ? 'Unsaved changes' : 'No unsaved changes'" marginTop="12" />
      <Button text="Save note" :isEnabled="!!note && !saving" @tap="save" />
    </StackLayout>
  </Page>
</template>

Cancel solicita navegação passando pelo guard. Keep editing mantém o editor montado com o rascunho. Discard fecha sem alterar a nota. Salvar devolve o valor ao detalhe, onde o código da aplicação atualiza notes.

Leave guards tratam a remoção de uma página, não sua cobertura por outra. No iOS, um guard ativo altera quais gestos podem fechar ou remover a página. Consulte a matriz de plataformas dos guards em vez de presumir que todo gesto pode esperar um diálogo.

5. Execute e confira o fluxo completo ​

No projeto NativeScript-Vue que você criou:

sh
ns run ios
# ou:
ns run android
  1. Digite coast em Find a note. Apenas Along the coast permanece.
  2. Abra a nota e toque em Edit in a modal.
  3. Altere o texto. Toque em Cancel → Keep editing e confirme que o rascunho continua lá.
  4. Toque em Save note. O detalhe original mostra o texto devolvido.
  5. Use o Back nativo. Home ainda contém coast e a lista continua filtrada.
  6. Abra o editor de novo, altere o texto e escolha Cancel → Discard. O último texto salvo permanece inalterado.

Se preferir a versão do repositório, siga a preparação do checkout/recursos e execute npm run tutorial:ios ou npm run tutorial:android no diretório demo. Passe -- --device <id> para selecionar o aparelho. O tutorial usa o ID do demo, então sua instalação substitui a outra entrada nesse aparelho.

O que construir depois ​

Agora você tem uma pilha preservada, uma pilha modal, um rascunho local e um resultado tratado pela aplicação. Reveja o modelo interativo de pilhas para separar esses ciclos de vida.

Continue com o Fieldnotes para adicionar drawer, busca nativa da página, ações compartilhadas entre header/menu e foco explícito de navegação. Escolha abas ou split quando o app precisar de outra estrutura. Adicione armazenamento persistente/restauração quando o estado precisar sobreviver ao encerramento do processo.

Publicado sob a licença MIT.