-
Notifications
You must be signed in to change notification settings - Fork 1
Capabilities pt BR
🌐 Esta página em: English · Português
O que a máquina consegue fazer (câmera, localização, fotos, biometria, a rede) como serviços tomados por construtor, realizados por host. Um componente pede uma capacidade e nunca aprende qual alvo respondeu.
public sealed class ScanShell(ICamera? camera, ILocation? location) : StatefulComponent
{
private async void LocateAsync()
{
if (location is null) return; // este host não tem
var here = await location.GetCurrentAsync();
}
}Anulável, sempre. Um host que não consegue fazer algo não registra nada, então o serviço resolve
para null e o app mostra aquilo que ele já sabe mostrar. Essa é a resposta do framework em todo
lugar: reportar a ausência em vez de fingir com um stub que falha mais tarde, num momento pior.
| Capacidade | O que responde | Desde |
|---|---|---|
IPhotoLibrary |
GetPermissionAsync(), PickImageAsync(), uma foto que o usuário escolheu |
0.2.0-preview.1 |
ICamera |
Capture() para uma foto, StartPreviewAsync() para uma ICameraSession (textura ao vivo) |
0.2.0-preview.1 |
ILocation |
GetCurrentAsync(), e Subscribe(…) para o fluxo de mudanças |
0.2.0-preview.1 |
IBiometrics |
IsAvailable, AuthenticateAsync(reason): Face ID / Touch ID / o da plataforma |
0.2.0-preview.1 |
IMotionSensor |
Subscribe(…) para leituras de movimento do dispositivo |
0.2.0-preview.1 |
INetworkStatus |
Current, e Subscribe(…): alcançabilidade e o que a transporta |
0.2.0-preview.1 |
ITextClipboard |
Read() / Write(text), para o botão de copiar de uma página |
0.2.0-preview.1 |
IThemeController |
O interruptor claro/escuro. Veja ServerIntegration | 0.2.0-preview.1 |
IAppStorage / ISecretStore
|
Preferências duráveis e segredos. Veja Storage | 0.2.0-preview.10 |
IClock |
Every(intervalo, onTick): o tempo, como algo que um componente pode assinar |
0.2.0-preview.31 |
Desde 0.2.0-preview.31
ILocation, IMotionSensor e INetworkStatus são as que MUDAM de resposta, então devolvem um
IDisposable do Subscribe em vez de um valor. Descarte-o quando o componente sair, ou a assinatura
sobrevive àquilo que ela atualizava.
O INetworkStatus reporta alcançabilidade como a plataforma a enxerga, não uma promessa de que
um host específico vai responder. Um app mostrando um "online" velho é pior que um que não diz nada.
O IClock é aquele cuja resposta é SÓ mudança, e é o que faltava para uma tela que avança sozinha:
um carrossel, um contador ao vivo, um status que faz polling. Todo o resto aqui reage a algo que uma
pessoa fez.
public sealed class Carousel(IClock clock) : StatefulComponent
{
private IDisposable? _tick;
private int _slide;
protected override void OnMount() =>
_tick = clock.Every(TimeSpan.FromSeconds(4), () => SetState(() => _slide = (_slide + 1) % 3));
protected override void OnUnmount() => _tick?.Dispose();
}O par é o contrato: o OnMount assina, o OnUnmount descarta, e sem a segunda metade cada navegação
deixa um timer rodando contra um componente que ninguém mais vê. No SERVIDOR o relógio nunca dispara,
e essa é a resposta certa em vez de uma que falta: um servidor renderiza um frame, então o primeiro
paint é o estado com que o componente foi construído e o tique começa na hidratação. Um telefone que
passou dez minutos no bolso volta com UM tique, nunca com seiscentos.
É um relógio PERIÓDICO e não um relógio de frame, de propósito. Animação por frame precisa de duas
coisas que ainda não existem, retenção de estado posicional pelo reconciler e um canal de geometria
no StyleChannels, e um callback a 60Hz sem elas seria a forma mais lenta de animar.
PermissionState tem quatro respostas, e a terceira é a que os apps esquecem:
-
NotDetermined: nunca perguntado. Perguntar mostra o prompt do sistema. -
Granted. -
Denied: recusado, e perguntar de novo não faz nada. Só os ajustes do sistema podem mudar isso, então um botão que repergunta é um botão que não faz nada; mande a pessoa para os ajustes. - A plataforma também pode reportar uma restrição que o usuário não consegue levantar de jeito nenhum.
No nativo, os textos que o SO mostra no prompt vêm do assembly:
[assembly: PhotonCapability("camera", "Escaneie um código de barras para adicionar um item.")]O build lê isso para o manifesto da plataforma (Info.plist, o manifesto do Android), então a razão
que o usuário lê é declarada uma vez, no app, ao lado do código que precisa dela.
Cruza por NOME, nunca por ordinal. O manifesto casa pela string da capacidade. Inserir um valor no meio de um enum uma vez transformou Location em Motion num manifesto publicado: o build ficou verde e o app pediu a permissão errada.
O browser realiza o que honestamente consegue (fotos por um file input; câmera, localização e rede pelas APIs web deles) e registra o resto como indisponível em vez de ausente, para que uma página que tome uma delas ainda receba um objeto e mostre o fallback, em vez de falhar ao construir.
Uma página toma o que precisa pelo construtor em todos os alvos:
public sealed class ProfilePage(IPhotoLibrary photos) : StatefulComponent
{
public override VisualNode Build(ComponentContext context) =>
photos.IsAvailable ? Button(label: "Escolher uma foto", onPressed: Pick) : Text("Sem biblioteca aqui");
}No nativo o ActivatorUtilities resolve. No browser o construtor transpilado resolve sozinho, pelo
NOME da interface: um tipo C# não existe em tempo de execução lá, mas IPhotoLibrary como string
existe, e os dois lados concordam nisso.
Seja qual for a forma do construtor. Um primary constructor seguia outro caminho no parser até a
0.2.0-preview.33, e uma seção escrita PairLoop(IClock clock) recebia
if (clock !== undefined) this.clock = clock: ninguém que a compõe no meio de uma árvore passa um
relógio, então o campo ficava indefinido e o componente ficava inerte, em silêncio, num alvo só.
E ninguém passa mesmo. A dependência sai do construtor emitido, então sai também do LOCAL DA
CHAMADA e da factory gerada: Quark(mood, size) é a assinatura inteira que quem chama vê, e
new Quark(clock, mood, size) em C# descarta o relógio na saída em vez de empurrar cada argumento
seguinte uma casa adiante. A regra é perguntada ao modelo, não adivinhada de um nome:
um parâmetro de construtor cujo tipo é uma INTERFACE é uma dependência, todo o resto é dado que quem
chama passa. Um componente toma aquilo que desenha (um rótulo, uma variante, um callback) e nenhum
desses é jamais uma interface. (IReadOnlyList<T> e IEnumerable<T> são dado, explicitamente.)
Desde 0.2.0-preview.18
public sealed class CopyButton : StatelessComponent
{
public override VisualNode Build(ComponentContext context) =>
context.GetService<ITextClipboard>() is { } clipboard
? IconButton(Icons.Copy, onPressed: () => clipboard.Write(_code))
: new Box(); // sem área de transferência aqui: desenhe nada, não um botão morto
}A injeção por construtor continua sendo a melhor resposta onde ela cabe: explícita, testável, legível
na assinatura. Mas ela só alcança a PÁGINA. Tudo abaixo precisava receber a mesma coisa na mão: um
cartão com um botão de copiar precisa de um ITextClipboard, então o artigo acima dele carregava um
que nunca usava, e a seção acima daquela também carregava. Um componente que ganhava uma necessidade
forçava uma edição em cada ancestral entre ele e a página.
O context.GetService<T>() responde nulo quando o alvo não tem a capacidade, que é a resposta que
quem chama qualquer capacidade tem que tratar de qualquer forma.
De ONDE ele resolve é assunto do host: o SSR usa o container da REQUISIÇÃO (então um registro scoped funciona e os registros da própria página vencem), o browser usa o que o boot registrou, um app Photon usa o do shell. O componente faz a mesma pergunta em todo lugar.
No WEB isso só chegou funcionando na 0.2.0-preview.21. O argumento de tipo era descartado na transpilação: a estratégia que transforma a chamada numa chave reconhecia o
RenderContextantigo do Core e umIServiceProvider, eComponentContextnão é nenhum dos dois, então a chamada caía no caminho de invocação comum, que descarta argumentos de tipo. Toda página pedia uma capacidade sem nome nenhum e recebia nulo de volta, justamente no alvo para o qual a feature existe.
Desde 0.2.0-preview.33
public sealed class PairLoop : StatefulComponent
{
private IDisposable? _tick;
protected override void OnMount() =>
_tick = GetService<IClock>()?.Every(Beat, () => SetState(() => _step++));
protected override void OnUnmount() => _tick?.Dispose();
}OnMount é onde uma inscrição pertence. Ele roda uma vez, na instância que o reconciler mantém, e
faz par com OnUnmount, que é o que impede uma página abandonada de seguir tiquetaqueando. Ele não
recebe ComponentContext, então antes disso o único jeito de alcançar uma capability em profundidade
era dentro do Build atrás de uma guarda de uma vez só: a inscrição morava no único método que o
framework chama repetidamente, e o par com OnUnmount parecia acidente.
O acessor no componente responde exatamente o que context.GetService<T>() responde, de qualquer
lugar do componente: um hook de ciclo de vida, um handler de evento, o próprio Build.
Desde 0.2.0-preview.33
O C# já diz, então o framework lê em vez de perguntar de novo:
| Escrito | Num alvo que tem | Num alvo que não tem |
|---|---|---|
IClock clock |
a capability | um erro nomeando a capability e o componente |
IClock? clock |
a capability |
null, para o componente tratar |
Um parâmetro não anulável é o componente prometendo que não funciona sem, então um null entregue a
ele falha depois, dentro de código que nunca menciona capabilities, no único alvo em que a tela está
quebrada. A mensagem nomeia qual capability, qual componente, e as duas saídas: registrar no host, ou
declarar o parâmetro anulável. Os dois alvos dizem isso, senão o browser seria o permissivo e o bug
só existiria lá.
Um arquivo com nullable desligado não dá sinal nenhum, e mantém o significado que sempre teve.
Desde 0.2.0-preview.33
using var _ = CapabilityScope.With<IClock>(fake);
var section = new PairLoop();
section.NotifyMounted();Uma capability, sobre o que já estiver em vigor, devolvida no dispose. Aninhar compõe. A cerimônia
que ela substitui era um resolver escrito à mão, que respondia null a toda OUTRA capability que o
componente pudesse pedir e precisava ser desfeito num finally que um teste podia esquecer.
Desde 0.2.0-preview.13
A mesma página tem que ser renderizável no servidor, e o servidor não tem câmera. Toda capacidade resolve lá para uma realização AUSENTE: ela se reporta indisponível e não devolve nada.
Sem isso a página não poderia nem ser construída: nenhum construtor que o container conseguisse satisfazer, e a requisição terminava em 500. A única página que faz algo era a única página que um crawler nunca via, e o visitante esperava o JavaScript só para ouvir que a página existe.
Ausente, deliberadamente, e não simulado. Não há câmera num datacenter e o localStorage do visitante
está na máquina do visitante, então um dublê no servidor seria pior que a falha: a página renderizaria
uma coisa, o browser hidrataria outra, e a divergência seria creditada ao reconciliador. A página toma
o ramo de disponibilidade que ela já precisa ter, e a capacidade real do boot do cliente substitui o
fallback no primeiro render do cliente.
O INetworkStatus é a exceção, e a exceção é o ponto: reportar offline gravaria o banner de offline
no markup que todo crawler e todo primeiro paint veem, para visitantes que acabaram de provar que
estão online ao buscar a página. Ele responde online.
Registrado com
TryAdd, então um app com uma resposta genuína no servidor (um armazenamento respaldado pela sessão do usuário) registra o dele e vence.
🌐 English · Português
🏁 Comece aqui
📱 Write-once
- Componentes write-once
- Superfície declarativa
- Motor Photon
- Design System
- Capacidades
- Armazenamento
- Formulários
- Editor de código
- Markdown
- Mermaid
- Renderização de Email
🏗️ Arquitetura
⚙️ Compilação
- Compilador
- Avaliação em tempo de compilação
- Recursos C# suportados
- Resolução de tipos externos
- Fluxo de build
- Diagnósticos
⚡ Runtime
🔌 Servidor
🎨 Ecossistema
🚀 Desenvolvimento