Skip to content

Capabilities pt BR

Edgar Mesquita edited this page Aug 18, 2026 · 6 revisions

Capacidades de dispositivo

🌐 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

Respostas que não ficam paradas

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.

Permissões

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 web

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.)

Um componente no meio de uma árvore

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 RenderContext antigo do Core e um IServiceProvider, e ComponentContext nã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.

Onde não há contexto

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.

O que uma capability ausente faz

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.

Entregando uma num teste

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.

Renderizando no servidor

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.

Clone this wiki locally