Usando a linguagem de script CSCS para desenvolvimento multiplataforma

Publicados: 2022-03-10
Resumo rápido ↬ Neste artigo, Vassili Kaplan explica como você pode usar uma linguagem de script para desenvolver aplicativos móveis multiplataforma. Você encontrará exemplos no iOS e no Android que incluem a colocação de widgets na tela, SQLite, solicitações da Web e análise de JSON.
Nosso objetivo não é construir uma plataforma; é cruzar todos eles.

— Mark Zuckerberg

CSCS (Customized Scripting in C#) é uma linguagem de script de código aberto implementada em C#. Sintaticamente, é muito semelhante ao JavaScript, mas também tem algumas semelhanças com o Python. Algumas dessas semelhanças são as palavras-chave na conhecida construção if…elif…else , e também têm a mesma definição de escopo de variável que em Python (por exemplo, uma variável definida dentro de um bloco if ou dentro de um loop também será visível fora) .

Ao contrário de JavaScript e Python, variáveis ​​e funções no CSCS não diferenciam maiúsculas de minúsculas. O objetivo principal do CSCS é permitir que o desenvolvedor escreva o mínimo de código possível . Além disso, o mesmo código é usado para o desenvolvimento iOS e Android. Além disso, o CSCS pode ser usado para desenvolvimento em Windows, Mac e Unity.

Nota : Você pode ler mais sobre como a Microsoft usa o CSCS em seu produto Maquette (baseado no Unity) aqui.

O CSCS pode ser adicionado ao seu projeto incorporando seu código-fonte C# em um projeto do Visual Studio Xamarin. Ao contrário da maioria das outras linguagens, você tem total propriedade do código-fonte do CSCS e pode adicionar ou modificar facilmente sua funcionalidade. Eu estarei compartilhando um exemplo disso mais tarde no artigo.

Além disso, vamos aprender como começar a usar o CSCS e usar alguns recursos mais avançados que foram abordados em outros artigos. Dentre esses recursos, vamos acessar um Web Service via Web Requests com análise de strings JSON, e também usaremos SQLite no iOS e Android.

A maneira mais fácil de começar é baixar uma amostra de um projeto usando CSCS e começar a jogar com o arquivo start.cscs . Isto é o que faremos na próxima seção: criando um aplicativo iOS/Android com GUI e eventos básicos.

Mais depois do salto! Continue lendo abaixo ↓

"Olá Mundo!" Em CSCS

Vamos começar com um exemplo relativamente simples de código CSCS que constrói uma tela com alguns widgets:

 AutoScale(); SetBackgroundColor("light_green"); locLabelText = GetLocation("ROOT", "CENTER", "ROOT", "TOP"); AddLabel(locLabelText, "labelText", "Welcome " + _DEVICE_INFO_ + " " + _VERSION_INFO_ + " User!", 600, 100); locTextEdit = GetLocation("ROOT", "LEFT", labelText, "BOTTOM"); AddTextEdit(locTextEdit, "textEdit", "Your name", 320, 80); locButton = GetLocation(textEdit,"RIGHT",textEdit, "CENTER"); AddButton(locButton, "buttonHi", "Hello", 160, 80); function buttonHi_click(sender, arg) { name = getText(textEdit); msg = name != "" ? "Hello, "+ name + "!" : "Hello, World!"; AlertDialog("My Great App", msg); }

A imagem abaixo mostra a interface de usuário resultante em um dispositivo iPhone e Android após clicar no botão “Hello” e não digitar nada no campo “Text Edit”:

Olá Mundo!" no iPhone (esquerda) e Android (direita)
"Olá Mundo!" no iPhone (esquerda) e Android (direita) (visualização grande)

Vamos examinar brevemente o código acima. Ele começa com a chamada da função AutoScale() , e o que isso faz é dizer ao analisador que os tamanhos dos widgets são relativos ao tamanho da tela, ou seja, eles serão redimensionados automaticamente (o widget parecerá maior em telas maiores e menor em telas menores telas). Essa configuração também pode ser substituída por widget.

Observe que não há necessidade de criar um manipulador especial com um clique de botão. Se você definir uma função com o nome widgetName_click() , ela será usada como manipulador quando o usuário clicar em um widget chamado widgetName (não precisa ser um botão, na verdade pode ser qualquer widget). É por isso que a função buttonHi_click() será acionada assim que o usuário clicar no botão.

Você deve ter notado que a GUI é construída completamente em código. Isso é feito fornecendo uma localização relativa do widget ao adicioná-lo. O formato geral de um comando de localização é o seguinte:

 location = GetLocation(WidgetX, HorizontalPlacement, WidgetY, VerticalPlacement, deltaX=0, deltaY=0, autoResize=true);

Assim, você pode colocar um widget em relação a outros widgets na tela. Um caso especial de widget é um widget “ROOT”, ou seja, a tela principal.

Depois de criar um local, você precisa fornecê-lo como um argumento para qualquer uma das seguintes funções:

  • AddLabel ,
  • AddButton ,
  • AddCombobox ,
  • AddStepper ,
  • AddListView ,
  • AddTextView ,
  • AddStepper ,
  • AddImageView ,
  • AddSlider ,
  • AddPickerView ,
  • e assim por diante.

Todos os itens acima têm a mesma estrutura:

 AddButton(location, newWidgetname, initialValue, width, height);

A largura e a altura do widget serão relativas ao tamanho da tela se o comando AutoScale() CSCS tiver sido executado anteriormente. Além disso, o valor inicial (no caso de um botão) é o texto mostrado nele. Isso pode ser alterado a qualquer momento invocando SetText(widgetName, newText) .

Usando o código do Visual Studio para depurar CSCS

Também podemos usar o Visual Studio Code para depurar scripts CSCS. Se você deseja desenvolver aplicativos para Android e iOS, precisa usar um Mac. Depois de instalar o Visual Studio Code, instale a extensão CSCS Debugger e REPL.

Para usar a extensão, adicione esta linha de código em qualquer lugar em seu script CSCS start.cscs :

 StartDebugger();

A imagem a seguir mostra como você pode usar o Visual Studio Code para depurar e alterar a funcionalidade do “Hello, World!” app que desenvolvemos na seção anterior. No próximo exemplo, adicionaremos um rótulo e um botão dinamicamente ao layout existente.

Para isso, basta selecionar o código a ser executado pelo analisador e pressionar Ctrl + 8 . Como resultado, um rótulo e um botão serão adicionados no centro da tela. Também adicionamos um manipulador de botão que atualizará o novo rótulo com a hora atual em cada clique no botão.

Alterando o layout rapidamente com o Visual Studio Code
Alterando o layout rapidamente com o Visual Studio Code (visualização grande)

Usando SQLite no CSCS

SQLite é um tipo ACID (Atomicity, Consistency, Isolation, Durability) de um banco de dados relacional, e foi desenvolvido por Richard Hipp (a primeira versão foi lançada em 2000). Ao contrário de outros bancos de dados relacionais, como Microsoft SQL Server ou Oracle Database, ele é incorporado. (Incorporado não apenas no dispositivo, mas também no programa final.) Está incluído no programa como uma biblioteca muito compacta, com menos de 500 KB de tamanho. Mas dois aplicativos (lançados pelo mesmo desenvolvedor) podem ler o mesmo banco de dados SQLite se o caminho do arquivo de banco de dados for conhecido por ambos os aplicativos.

A vantagem do SQLite é que ele pode ser usado sem uma instalação extra em um dispositivo iOS ou Android. A desvantagem é que ele obviamente não pode armazenar tantos dados quanto um banco de dados “normal” e também que é fracamente tipado (ou seja, você pode inserir uma string em vez de um inteiro — ele será convertido para um inteiro ou 0 em caso de falha). Por outro lado, este último também pode ser visto como uma vantagem.

SQLite pode ser facilmente usado a partir do CSCS sem instruções extras de importação. Aqui está uma tabela que o ajudará a obter uma visão geral das principais funções SQLite usadas no CSCS:

Comando Descrição
SQLInit(DBName) Inicializa um banco de dados ou define um banco de dados a ser usado com instruções DB conseqüentes.
SQLDBExists(DBName) Verifica se o banco de dados foi inicializado. Também define o banco de dados a ser usado com instruções DB conseqüentes.
SQLQuery(query) Executa uma consulta SQL (uma instrução select). Retorna uma tabela com registros.
SQLNonQuery(nonQuery) Executa uma não consulta SQL, por exemplo, uma instrução de atualização, criação ou exclusão. Retorna o número de registros afetados.
SQLInsert(tableName, columnList, data) Insere a tabela passada de dados de registros na tabela de banco de dados especificada. O argumento columnList tem a seguinte estrutura: colName1,colName2,…,colNameN

Tabela 1: Comandos SQLite no CSCS

É assim que as SQLInit() e SQLDBExists() são normalmente usadas:

 DBName = "myDB.db1"; if (!SQLDBExists(DBName)) { create = "CREATE TABLE [Data] (Symbol ntext, Low real, High real, Close real, Volume real, Stamp text DEFAULT CURRENT_TIMESTAMP)"; SQLNonQuery(create); } SQLInit(DBName);

Veremos mais exemplos de como você pode selecionar e inserir dados em um banco de dados SQLite posteriormente. Mostrarei um exemplo de como gravar dados de estoque que foram extraídos de um Web Service em um banco de dados SQLite local.

Adicionando funcionalidade personalizada ao CSCS

Nesta seção, veremos como você pode estender a funcionalidade do CSCS. Como exemplo, veremos a implementação existente da função CSCS Sleep abaixo.

Para adicionar funcionalidade personalizada, tudo o que você precisa fazer é criar uma nova classe derivando da classe ParserFunction , substituindo seu método Evaluate() e registrando essa classe com o analisador. Aqui está uma versão curta (sem verificação de erros):

 class SleepFunction : ParserFunction { protected override Variable Evaluate(ParsingScript script) { List args = script.GetFunctionArgs(); int sleepms = Utils.GetSafeInt(args, 0); Thread.Sleep(sleepms); return Variable.EmptyInstance; } } class SleepFunction : ParserFunction { protected override Variable Evaluate(ParsingScript script) { List args = script.GetFunctionArgs(); int sleepms = Utils.GetSafeInt(args, 0); Thread.Sleep(sleepms); return Variable.EmptyInstance; } }

O registro de uma classe com o analisador pode ser feito em qualquer ponto da fase de inicialização através do seguinte comando:

 ParserFunction.RegisterFunction("Sleep", new SleepFunction());

É isso! Agora o método Evaluate() da classe SleepFunction será invocado assim que um token “Sleep” for extraído pelo analisador.

Observe que CSCS não diferencia maiúsculas de minúsculas (exceto as instruções de fluxo de controle principais: if , elif , else , for , while , function , include , new , class , return , try , throw , catch , break , continue ). Isso significa que você pode digitar “sleep(100)” ou “Sleep(100)” — ambas as chamadas suspenderão o thread em execução por 100 milissegundos.

Processando JSON no CSCS

JSON (JavaScript Object Notation) é um formato leve de intercâmbio de dados, consistindo em pares atributo-valor e pares tipo array. Foi desenvolvido por Douglas Crockford no início dos anos 2000 (na mesma época em que o SQLite também apareceu).

Nesta seção, aprenderemos como analisar JSON usando CSCS.

A função CSCS para analisar uma string JSON é GetVariableFromJSON(jsonText) . Essa função retorna uma tabela de hash na qual as chaves são os atributos da string JSON.

Considere o seguinte exemplo de uma string JSON:

 jsonString = '{ "eins" : 1, "zwei" : "zweiString", "mehr" : { "uno": "dos" }, "arrayValue" : [ "une", "deux" ] }';

Após invocar:

 a = GetVariableFromJSON();

A variável a será uma tabela de hash com os seguintes valores:

 a["eins"] = 1 a["zwei"] = "zweiString" a["mehr"]["uno"] = "dos" a["arrayValue"][0] = "une" a["arrayValue"][1] = "deux"

Na próxima seção, veremos outro exemplo de análise de uma string JSON de um Web Service.

Um exemplo de um aplicativo com SQLite, solicitações da Web e JSON

Para um aplicativo usando SQLite, um Web Service e análise JSON, usaremos o Alpha Vantage Web Service. Você pode obter uma chave de API gratuitamente, mas a versão gratuita permite acessar o serviço da web não mais que 5 vezes por minuto.

Usando o Alpha Vantage, você pode extrair vários conjuntos de dados financeiros — incluindo preços de ações. Isso é o que vamos fazer em nosso aplicativo de exemplo.

A imagem abaixo mostra a aparência dos aplicativos Stocks em um dispositivo iOS e Android.

Extraindo ações do Alpha Vantage Web Service no iOS (esquerda) e Android (direita)
Extraindo ações do Alpha Vantage Web Service no iOS (esquerda) e Android (direita) (visualização grande)

O código CSCS para construir a GUI é o seguinte:

 locLabel = GetLocation("ROOT","CENTER", "ROOT","TOP", 0,30); AddLabel(locLabel, "labelRefresh", "", 480, 60); locSFWidget = GetLocation("ROOT","CENTER", labelRefresh,"BOTTOM"); AddSfDataGrid(locSFWidget, "DataGrid", "", graphWidth, graphHeight); listCols = {"Symbol","string", "Low","number", "High", "number", "Close","number", "Volume","number"}; AddWidgetData(DataGrid, listCols, "columns"); colWidth = {17, 19, 19, 19, 26}; AddWidgetData(DataGrid, colWidth, "columnWidth"); locButton = GetLocation("ROOT","CENTER",DataGrid,"BOTTOM"); AddButton(locButton, "buttonRefresh", "Refresh", 160, 80); locLabelError = GetLocation("ROOT","CENTER","ROOT","BOTTOM"); AddLabel(locLabelError, "labelError", "", 600, 160); SetFontColor(labelError, "red"); AlignText(labelError, "center"); getDataFromDB();

O método getDataFromDB() extrairá todos os dados do banco de dados SQLite. Ele usa a consulta SQL definida da seguinte forma:

 query = "SELECT Symbol, Low, High, Close, Volume, DATETIME(Stamp, 'localtime') as Stamp FROM Data ORDER BY Stamp DESC LIMIT 5;";

Dê uma olhada no código abaixo para a implementação getDataFromDB() .

 function getDataFromDB() { results = SQLQuery(query); for (i = 1; i < results.Size; i++) { vals = results[i]; stock = vals[0]; low = Round(vals[1], 2); high = Round(vals[2], 2); close = Round(vals[3], 2); volume = Round(vals[4], 2); refresh = vals[5]; stockData = {stock, low, high, close, volume}; AddWidgetData(DataGrid, stockData, "item"); } SetText(labelRefresh, "DB Last Refresh: " + refresh); lockGui(false); }

Agora vamos ver como obtemos dados do Alpha Vantage Web Service. Primeiro, inicializamos os dados:

 baseURL = "https://www.alphavantage.co/query? " + "function=TIME_SERIES_DAILY&symbol="; apikey = "Y12T0TY5EUS6BC5F"; stocks = {"MSFT", "AAPL", "GOOG", "FB", "AMZN"}; totalStocks = stocks.Size;

Em seguida, carregamos as ações uma a uma assim que o usuário clica no botão “Atualizar”:

 function buttonRefresh_click(object, arg) { lockGui(); SetText(labelRefresh, "Loading ..."); SetText(labelError, ""); ClearWidget(DataGrid); loadedStocks = 0; getData(stocks[loadedStocks]); } function getData(symbol) { stockUrl = baseURL + symbol + "&apikey=" + apikey; WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure"); }

Aqui está a principal função CSCS a ser usada para obter dados de um Web Service:

 WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure");

Os dois últimos parâmetros são funções a serem invocadas na conclusão da solicitação da web. Por exemplo, em caso de falha, a seguinte função CSCS será chamada:

 function OnFailure(object, errorCode, text) { SetText(labelError, text); lockGui(false); }

Como resultado, o usuário receberá uma mensagem de erro conforme mostrado abaixo:

Um erro ao solicitar dados da web
Um erro ao solicitar dados da web (visualização grande)

Mas, se tudo estiver bem, vamos analisar a string JSON e inserir seu conteúdo no banco de dados SQLite.

 function OnSuccess(object, errorCode, text) { jsonFromText = GetVariableFromJSON(text); metaData = jsonFromText[0]; result = jsonFromText[1]; symbol = metaData["2. Symbol"]; lastRefreshed = metaData["3. Last Refreshed"]; allDates = result.keys; dateData = result[allDates[0]]; high = Round(dateData["2. high"], 2); low = Round(dateData["3. low"], 2); close = Round(dateData["4. close"], 2); volume = dateData["5. volume"]; stockData = {symbol, low, high, close, volume}; SQLInsert("Data","Symbol,Low,High,Close,Volume",stockData); if (++loadedStocks >= totalStocks) { getDataFromDB(); } else { getData(stocks[loadedStocks]); } }

Para entender como acessamos diferentes campos na tabela de hash acima, vamos dar uma olhada na string real recebida da solicitação da web do Alpha Vantage:

 { "Meta Data": { "1. Information": "Daily Prices (open, high, low, close) and Volumes", "2. Symbol": "MSFT", "3. Last Refreshed": "2019-10-02 14:23:20", "4. Output Size": "Compact", "5. Time Zone": "US/Eastern" }, "Time Series (Daily)": { "2019-10-02": { "1. open": "136.3400", "2. high": "136.3700", "3. low": "133.5799", "4. close": "134.4100", "5. volume": "11213086" }, … } }

Como você pode ver, obtemos a data mais recente como o primeiro elemento do array allDates que consiste em todas as datas extraídas.

Conclusão

Adicionar CSCS ao seu projeto é fácil. Tudo o que você precisa fazer é simplesmente incorporar o código-fonte do CSCS como um módulo ao seu projeto — assim como é feito em um projeto Xamarin de exemplo.

Você usa e estende a linguagem de script CSCS em seus projetos? Deixe um comentário abaixo - ficarei feliz em ouvir de você!

Leitura adicional

Se você quiser explorar um pouco mais a linguagem CSCS, aqui estão alguns dos artigos que escrevi sobre o tema:

  • “Um analisador de expressão de divisão e mesclagem em C#”, MSDN Magazine (outubro de 2015)
  • “Scripts personalizáveis ​​em C#”, MSDN Magazine (fevereiro de 2016)
  • “Escrevendo aplicativos móveis nativos usando uma linguagem de script personalizável”, MSDN Magazine (fevereiro de 2018)
  • “CSCS: scripts personalizados em C#,” GitHub
  • “Desenvolvendo aplicativos nativos multiplataforma com uma linguagem de script funcional”, CODE Magazine
  • “Implementando uma linguagem personalizada de forma sucinta” (eBook)
  • “Escrevendo aplicativos móveis nativos em uma linguagem funcional de forma sucinta” (eBook)

Como recurso adicional, também recomendo ler como você pode melhorar o desempenho do CSCS pré-compilando suas funções.