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
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
IConsent
State, Grant(), Deny(): a resposta do visitante ao rastreamento não essencial, o cookie que o instalador do GTM condiciona. Veja Analytics próxima release
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;
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.
•
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.)
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.
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.
Desde 0.2.0-preview.33
using var _ = CapabilityScope.With<IClock>(fake);
var section = new PairLoop();
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.