Flutter: Web Share API não suportada!
Estava usando o flutter_inappwebview: ^6.0.0-beta.23 para fornecer a WebView, quando cliquei em um botão de "compartilhar" no site apresentado por ela, adivinha? Não aconteceu nada!
Pois é, quando reparei nos logs, apareceu a mensagem: "Web Share API não suportada", então começou minha caçada pela solução, até que encontrei alguns contéudos que pareciam ser uma solução, só que envolvia mexer no código nativo e ainda não era em Flutter, sem contar que é respostas antigas também.
Dei uma analisada na doc da Share API e não tive ideia alguma.
Até que veio a solução...
O flutter_inappwebview permite injetar código Javascript na WebView utilizando o evaluateJavascript.
Então o que eu injeitei o seguinte código depois do onLoadStop:
navigator.share = (data) => {
const reader = new FileReader();
reader.onload = function(event) {
const base64String = event.target.result.split(',')[1];
console.log("SHARE: " + JSON.stringify({
title: data.title,
text: data.text,
file: base64String,
fileName: data.files[0].name
}));
};
reader.readAsDataURL(data.files[0]);
A primeira linha navigator.share = (data) => { está redefinindo a função navigator.share e substituindo-a por uma nova implementação.
const reader = new FileReader(); cria uma nova instância da classe FileReader, que permite ler o conteúdo de arquivos selecionados pelo usuário.
reader.onload = function(event) { ... } define um evento de carga (onload) para o leitor de arquivos. Quando o arquivo for carregado com sucesso, a função dentro desta definição será executada.
const base64String = event.target.result.split(',')[1]; obtém o resultado do arquivo lido pelo leitor de arquivos (event.target.result). O resultado é uma URL de dados (data URL) que contém tanto o tipo do arquivo como seu conteúdo em base64. Aqui, estamos separando a URL de dados em duas partes e pegando apenas a parte que contém o conteúdo em base64.
console.log("SHARE: " + JSON.stringify({ ... })); gera uma mensagem de log para mostrar os dados de compartilhamento formatados. Nesse caso, a mensagem conterá o título, texto, conteúdo do arquivo em base64 e o nome original do arquivo.
reader.readAsDataURL(data.files[0]); inicia a leitura do arquivo selecionado pelo usuário (data.files[0]). Ao chamar readAsDataURL, o leitor de arquivos lerá o conteúdo do arquivo como uma URL de dados (data URL).
Em resumo, esse código substitui a função navigator.share por uma versão personalizada que permite compartilhar arquivos em base64, além de texto e URLs. Ele lê o arquivo selecionado, converte o conteúdo para base64 e, em seguida, imprime os detalhes de compartilhamento no console. Essa abordagem amplia as capacidades de compartilhamento de arquivos da função original navigator.share.
Fazendo isso eu consigo, obter o que está no console.log através do onConsoleMessage do flutter_inappwebview.
O código Flutter ficou assim:
void onConsoleMessage(
InAppWebViewController controller,
ConsoleMessage consoleMessage,
) async {
if (consoleMessage.message.startsWith('SHARE: ') == false) return;
final data = json.decode(consoleMessage.message.substring(7));
String? title = data['title'];
String? subtitle = data['text'];
String? fileName = data['fileName'];
String? fileAsBase64 = data['file'];
final uint8List = base64Decode(fileAsBase64!);
final tempDir = await getTemporaryDirectory();
File image = await File('${tempDir.path}/$fileName').create();
final file = await image.writeAsBytes(uint8List);
Share.shareXFiles(
[XFile(file.path)],
text: title,
subject: subtitle,
);
}
void onConsoleMessage(InAppWebViewController controller, ConsoleMessage consoleMessage) async { ... }: Esta função recebe dois parâmetros: o primeiro é o controlador do InAppWebView, que permite interagir com a webview, e o segundo é a mensagem do console vinda da web.
if (consoleMessage.message.startsWith('SHARE: ') == false) return;: Aqui, estamos verificando se a mensagem do console começa com "SHARE: ". Se não começar, a função retorna, ou seja, não faz nada. Essa verificação permite que a função identifique se a mensagem do console é referente a uma ação de compartilhamento.
final data = json.decode(consoleMessage.message.substring(7));: Esta linha decodifica a mensagem do console, removendo a parte "SHARE: " e transformando o restante em um objeto Dart usando json.decode(). Isso permite que os dados de compartilhamento sejam acessados individualmente.
String? title = data['title']; String? subtitle = data['text']; String? fileName = data['fileName']; String? fileAsBase64 = data['file'];: Aqui, estamos extraindo os valores das propriedades do objeto data, que contêm as informações do compartilhamento. Essas informações são o título, subtítulo, nome do arquivo e o conteúdo do arquivo em base64.
final uint8List = base64Decode(fileAsBase64!);: Decodificamos o conteúdo do arquivo em base64, convertendo-o de volta para uma lista de bytes (Uint8List).
final tempDir = await getTemporaryDirectory(); File image = await File('${tempDir.path}/$fileName').create();: Obtemos o diretório temporário do dispositivo usando getTemporaryDirectory() e criamos um arquivo dentro desse diretório com o nome do arquivo recebido.
final file = await image.writeAsBytes(uint8List);: Escrevemos os bytes decodificados no arquivo que criamos anteriormente.
Share.shareXFiles([XFile(file.path)], text: title, subject: subtitle);: Finalmente, usamos o pacote share_plus para compartilhar o arquivo criado. Utilizamos a função shareXFiles e passamos o caminho do arquivo como XFile, além do título e subtítulo como parâmetros opcionais.
Ou seja, podemos compartilhar arquivos a partir de uma webview em um aplicativo Flutter, decodificando e salvando o arquivo em um local temporário antes de compartilhá-lo com outros aplicativos do dispositivo.
Na WebView ficou assim:
onLoadStop: (control, url) async {
control.evaluateJavascript(
source: '''navigator.share = (data) => {
const reader = new FileReader();
reader.onload = function(event) {
const base64String = event.target.result.split(',')[1];
console.log("SHARE: " + JSON.stringify({
title: data.title,
text: data.text,
file: base64String,
fileName: data.files[0].name
}));
};
reader.readAsDataURL(data.files[0]);
}''',
);
},
onConsoleMessage: onConsoleMessage,
E pronto! É uma solução rápida! Espero ter ajudado!