eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
Docspt-BR
Capacidades de dispositivo
Edit this page
6 min read
🌐 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.
1
2
3
4
5
6
7
8
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
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.
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:
1
[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:
1
2
3
4
5
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. 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
1
2
3
4
5
6
7
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.
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.