Neste artigo, criaremos um aplicativo de celular que executará em qualquer dispositivo Android. Você terá que instalar o Android SDK; Android V1.5 SDK foi usado aqui. O código do aplicativo será escrito na linguagem de programação Scala. Se você nunca viu Scala, não haverá problema porque o código Scala será explicado. No entanto, é recomendado que você seja pelo menos familiar com a linguagem Java caso não seja familiar com a Scala. Scala V2.7.5 foi usado para desenvolver o código neste artigo. Para Android e Scala, existem excelentes plug-ins Eclipse disponíveis. Eclipse V3.4.2 foi usado, além da Android Development Tools (ADT) V0.9.1 e V2.7.5 da Scala IDE plug-in. Veja Recursos para todas estas ferramentas.
Escrever um aplicativo Android pode soar como uma proposta complicada. Aplicações Android executam suas próprias máquinas virtuais: a máquina virtual Dalvik. No entanto, o caminho para a construção de um aplicativo Android abrirá coisas para nós. A estratégia básica que usaremos está ilustrada abaixo.
Figura 1. Caminho para construção para Scala no Android
A idéia é primeiramente compilar todos os códigos Scala para arquivos de classe Java. É o que o compilador do Scala faz, assim não tem nada complicado sobe isso. A seguir, usaremos o compilador dex do Android para compilar os arquivos de classe Java para o formato usado pelo Dalvik VM em um dispositivo Android. Isto é conhecido como dexing e é o caminho normal de compilação .para um aplicativo Android. Normalmente, você vai de um arquivo .java para um arquivo .class para um simples arquivo .dex. A única coisa diferente neste caso é que iniciamos com arquivos .scala. Finalmente, o arquivo .dex e outros recursos de aplicação são compactados como um arquivo APK que pode ser instalado em um dispositivo Android.
Então como fazemos tudo isso acontecer? Usaremos o Eclipse para fazer a maior parte do trabalho pesado. No entanto, existe um passo complicado: Para que nosso código seja executado, é também necessário código da biblioteca padrão Scala. Numa instalação típica Scala, este é um simples SAR encontrado em /lib/scala-library.jar. Este JAR, no entanto, inclui certo código não suportado no Android. Alguns códigos necessitam serem um pouco ajustados, e alguns devem ser removidos. Uma construção customizada de scala-library.jar funciona melhor, pelo menos por agora. Veja Recursos para a construção customizada usada aqui. Faremos referência a este JAR como a biblioteca Android JAR.
Uma vez que tivermos o JAR, o resto é bem fácil. Apenas crie um projeto Android usando o ADT plug-in para Eclipse. Após acrescente a natureza Scala ao projeto. Substitua a biblioteca padrão Scala pela biblioteca Android discutida acima. Finalmente, acrescente o diretório output em seu caminho de classe. Agora você está pronto para começar. Isto é descrito mais detalhado no site principal do Scala (veja Recursos). Agora que temos a configuração básica, vamos dar uma olhada no aplicativo Android que criaremos usando Scala.
Agora que sabemos como pegar o código Scala e transformá-lo no formato binário que será executado em um dispositivo Android, é hora de criar um aplicativo de celular usando Scala. O aplicativo que criaremos será um aplicativo simples de conversão de unidade. Isso permitirá que os usuários convertam de Inglês para unidades métricas e vice-versa. Este é um aplicativo simples, porém veremos como mesmo os mais simples aplicativos podem se beneficiar do uso do Scala. Vamos começar olhando para os elementos layout do UnitsConverter.
Você pode estar contente escrevendo Scala que será executado em seu celular, porém não toda codificação de desenvolvimento de celular deve ser feita em Scala, ou na linguagem Java. O Android SDK fornece um ótimo caminho para separar o código usuário-interface da lógica do aplicativo usando um sistema de layout baseado em XML. Vamos dar uma olhada no arquivo principal de layout para nosso aplicativo, mostrado no Listing 1.
Lista 1. Layout principal do aplicativo Converter
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:gravity="center_horizontal" android:padding="10px"
>
<TextView android:id="@+id/prompt_label" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/prompt_metric"/>
<EditText android:id="@+id/amount" android:layout_below="@id/prompt_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/uom_label"
android:layout_below="@id/amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/uom"/>
<Spinner android:id="@+id/uom_value"
android:layout_below="@id/uom_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button android:id="@+id/convert_button"
android:layout_below="@id/uom_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/convert_button_label"/>
<TextView android:id="@+id/result_value"
android:layout_below="@id/convert_button"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</RelativeLayout>
|
O código acima cria claramente a principal UI para o aplicativo. Seu nó root é um
RelativeLayout elemento contêiner. Existem várias opções de layout disponíveis no Android SDK. A
RelativeLayout simplesmente instrui o tempo de execução a distribuir vários widgets UI usando posicionamento relativo. Para usar posicionamento relativo, adicionamos um elemento visível — neste caso, um
TextView elemento. Este é um simples elemento para exibir texto. É atribuído um ID para
prompt_label. Este é usado pelo próximo elemento, um EditText elemento (uma caixa de texto). Este elemento contém layout_below atributo para o qual o valor é igual ao prompt_label ID. Em outras palavras, EditText deve ser colocado abaixo do elemento chamadoprompt_label.
O restante do código layout é bem simples. Juntando tudo, existe uma caixa de entrada de texto com um rótulo, um spinner (uma caixa de combinação ou uma lista suspensa) com um rótulo, um botão, e uma outra área de texto para saída. Figura 2 mostra a figura do aplicativo sendo executado, com elementos identificados.
Figura 2. Um lLayout Android — dissecado
De onde vêem os vários valores de texto vistos acima? Verifique que vários elementos no Listing 1 têm um texto de atributo. Por exemplo, o prompt_label elemento tem um texto de atributo igual ao @string/prompt_metric. Isso indica que está usando um dos arquivos de recurso padrão em um aplicativo Android: o arquivo strings.xml, visto no Listing 2.
Lista 2. O recurso do strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="prompt_metric">Enter amount (KM, g, L, C)</string>
<string name="prompt_english">Enter amount (miles, lbs, gallons,
F)</string>
<string name="uom">Units of Measure</string>
<string name="convert_button_label">Convert</string>
<string name="app_name">Converter</string>
<string name="english_units">English</string>
<string name="metric_units">Metric</string>
</resources>
|
Agora você pode ver de onde vêem todo o testo na Figura 2. O spinner tem uma lista suspensa de possíveis unidades de medida para usar, que não são listadas no Listing 2. Ao contrário, elas vêem de outro arquivo, arrays.xml, visto no Listing 3.
Lista 3. O recurso do arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="english_units">
<item>Fahrenheit</item>
<item>Pounds</item>
<item>Ounces</item>
<item>Fluid Ounces</item>
<item>Gallons</item>
<item>Miles</item>
<item>Inches</item>
</array>
<array name="metric_units">
<item>Celsius</item>
<item>Kilograms</item>
<item>Grams</item>
<item>Millileters</item>
<item>Liters</item>
<item>Kilometers</item>
<item>Centimeters</item>
</array>
</resources>
|
Agora podemos ver os valores que serão usados para o spinner. Como estes na verdade terminam no spinner, e como o aplicativo muda de Inglês para unidades métricas? Para responder estas questões, devemos olhar para o próprio código do aplicativo.
O código do aplicativo para o aplicativo de Conversor é muito simples — em qualquer linguagem. Ele poderia, é claro, ser facilmente feito na linguagem Java, mas não é mais complexo fazer no Scala. De fato, como veremos, muitas coisas podem ser feitas mais facilmente usando Scala. Vamos começar olhando o código por trás da UI que estivemos examinando.
O código por trás da visualização
O modo mais fácil de explicar o código Scala que auxilia a criação da UI é olhar para ele e ir através dele. Para qualquer aplicativo, você define a atividade padrão do seu aplicativo no arquivo AndroidManifest.xml do aplicativo. O backup de um UI é feito por um
Activity classe, e o padrão
Activity define o
Activity classe executado quando o aplicativo é inicialmente carregado. Para um aplicativo simples como esse, esta é a única atividade necessária. Para este aplicativo, a classe chamada Converter é listada, e seu código de fonte é mostrado no Listing 4.
Lista 4. A classe de atividade
Converter
class Converter extends Activity{
import ConverterHelper._
private[this] var amountValue:EditText = null
private[this] var uom:Spinner= null
private[this] var convertButton:Button = null
private[this] var resultValue:TextView = null
override def onCreate(savedInstanceState:Bundle){
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
uom = findViewById(R.id.uom_value).asInstanceOf[Spinner]
this.setUomChoice(ENGLISH)
amountValue = findViewById(R.id.amount).asInstanceOf[EditText]
convertButton = findViewById(R.id.convert_button).asInstanceOf[Button]
resultValue = findViewById(R.id.result_value).asInstanceOf[TextView]
convertButton.setOnClickListener( () => {
val unit = uom.getSelectedItem.asInstanceOf[String]
val amount = parseDouble(amountValue.getText.toString)
val result = UnitsConverter.convert(Measurement(unit,amount))
resultValue.setText(result)
})
}
override def onCreateOptionsMenu(menu:Menu) = {
super.onCreateOptionsMenu(menu)
menu.add(NONE, 0, 0, R.string.english_units)
menu.add(NONE, 1, 1, R.string.metric_units)
true
}
override def onMenuItemSelected(featureId:Int, item:MenuItem) = {
super.onMenuItemSelected(featureId, item)
setUomChoice(if (item.getItemId == 1) METRIC else ENGLISH)
true
}
private
def setUomChoice(unitOfMeasure:UnitsSystem){
if (uom == null){
uom = findViewById(R.id.uom_value).asInstanceOf[Spinner]
}
val arrayId = unitOfMeasure match {
case METRIC => R.array.metric_units
case _ => R.array.english_units
}
val units = new ArrayAdapter[String](this, R.layout.spinner_view,
getResources.getStringArray(arrayId))
uom.setAdapter(units)
}
}
|
Vamos começar no começo desta classe. Isso estende
android.app.Activity. Isso é uma classe Java, mas você pode subclassificar classes Java a partir do Scala. A seguir, isso tem várias variáveis de instância. Cada uma delas corresponde a um elemento da UI definido mais cedo. Verifique que o escopo de cada é feito como
private[this]. Isso demonstra o nível de controle de acesso no Scala que não existe na linguagem Java. As variáveis não são apenas privadas como são privadas à instância particular do Converter classe. Este nível de controle de acesso pode ser um overkill para um aplicativo de celular, porém se você for um desenvolvedor de Scala, é seguro que toda a sintaxe que você está acostumado ainda está disponível no aplicativo Android.
Voltando ao código no Listing 4, verifique que substituímos o
onCreate método. Este é um método definido no Activity classe e é normalmente substituído por qualquer personalizado. Activity. Se estiver escrevendo este código na linguagem Java, você deve adicionar um @Override
anotação. No Scala, substituir é uma palavra chave, exigida para assegurar correção. Isto preveniria um erro comum como soletrar um nome de método incorretamente. Se soletrar incorretamente, o compilador do Scala o encontraria e acusaria um erro. Verifique que neste método, assim como todos os outros, você não precisa declarar um tipo de retorno. O compilador do Scala é capaz de inferir esta informação, assim não havendo necessidade se ser redundante.
A maior parte do código no onCreate é similar ao que você faria na linguagem Java. Existem algumas coisas de interesse. Verifique que usamos o método findViewById
(definido na superclasse Activity) para obter um identificador em vários elementos UI. Este método não é seguro e requer um cast. Para typecast no Scala, use o método parametrizado
asInstanceOf[T], onde
T é o tipo para o qual você está convertendo. O cast é funcionalmente como seria na linguagem Java; O Scala somente possui uma melhor sintaxe para isso. A seguir, verifique a chamada para setUomChoice
(entraremos brevemente no detalhe deste método). Finalmente, verifique que obtivemos um identificador no botão que criamos no layout XML e adicione um manipulador de clique de evento.
Se isso foi escrito na linguagem Java, nós deveríamos passar na implementação da interface Android
OnClickListener. A interface define um único método: onClick. Na verdade, tudo que você se preocupa é aquele método, porém não existe um caminho para passar diretamente em um método na linguagem Java. Não é o caso no Scala, onde somos capazes de passar em um método literal, ou encerramento. Aqui, determinamos um encerramento usando a sintaxe.
() => { ... }, onde o corpo do método são os conteúdos entre parênteses. O abrir/fechar parênteses determina uma função que não leva parênteses. No entanto, estamos passando este encerramento para o setOnClickListener método na instância do
Button, uma classe Java definida no Android SDK. Como podemos passar um encerramento do Scala para um API Java? Vamos dar uma olhada.
Programação funcional no Android
Para entender como conseguimos que o Android APIs trabalhe com funções literais, olhe para a primeira linha de Converter definição classe. Esta é uma instrução importante. Este é um outro ótimo recurso do Scala. Você pode importar pacotes, classes, etc. em qualquer parte do código, e o escopo é feito onde você os importa. Neste caso, estamos importando tudo de uma coisa chamada
ConverterHelper. O código para
ConverterHelper é mostrado no Listing 5.
Lista 5. O
ConverterHelper
object ConverterHelper{
import android.view.View.OnClickListener
implicit def funcToClicker(f:View => Unit):OnClickListener =
new OnClickListener(){ def onClick(v:View)=f.apply(v)}
implicit def funcToClicker0(f:() => Unit):OnClickListener =
new OnClickListener() { def onClick(v:View)=f.apply}
}
|
Este é um singleton do Scala porque usa a declaração do objeto ao invés da declaração de classe. O padrão singleton é incluído diretamente no Scala e é usado em qualquer parte onde você usaria métodos estáticos ou variáveis na linguagem Java. Neste caso, o singleton apenas contém um par de funções:
funcToClicker e
funcToClicker0. Isto toma a função como um parâmetro input e retorna uma instância de
OnClickListener, a interface definida no Android SDK. Por exemplo: funcToClicker é definido como tomando uma função f. A função
f é tipo como uma função que leva um único parâmetro input do tipo View (outra classe do Android) e retorna Unit, o que é equivalente ao cancelar no Scala. Depois retorna uma implementação do
OnClickListener, onde este método de interface
onClick é implementado simplesmente aplicando a função input f para o
View parâmetro. A outra função,
funcToClick0, faz a mesma coisa, porém leva uma função que não tem o parâmetro input.
Ambas funções (funcToClicker e
funcToClicker0) são definidas como implícitas. Este é um recurso conveniente do Scala. Isso permite tipos serem implicitamente convertidos em outro tipo pelo compilador. Neste caso, quando o compilador analisa o Converter método classe
onCreate , ele encontra uma chamada para
setOnClickListener. Este é um método que requer uma instância de OnClickListener. No entanto, ao invés, o compilador encontra a função. Antes de reclamar e a compilação falhar, o compilador verifica para ver se existem funções implícitas em escopo que permitiria ele converter a função para um OnClickListener. De fato, existe, então ele executa esta conversão, e tudo está bem. Agora que entendemos como usar encerramentos dentro do Android, vamos dar mais uma olhada na lógica do aplicativo — em particular, como os cálculos de unidade de conversão são realizados .
Conversões e cálculos de unidade
Vamos voltar para o Listing 4. A função passada para
onClickListener recupera as unidades de medida e os valores inseridos pelo usuário. Ele depois cria um
Measurement instância e passa para um objeto chamado UnitsConverter. O código para estas entidades é mostrado no Listing 6.
Lista 6.
Measurement e
UnitsConverter
case class Measurement(uom:String, amount:Double)
object UnitsConverter{
// constants
val lbToKg = 0.45359237D
val ozToG = 28.3495231
val fOzToMl = 29.5735296
val galToL = 3.78541178
val milesToKm = 1.609344
val inchToCm = 2.54
def convert (measure:Measurement)= measure.uom match {
case "Fahrenheit" => (5.0/9.0)*(measure.amount - 32.0) + " C"
case "Pounds" => lbToKg*measure.amount + " kg"
case "Ounces" => ozToG*measure.amount + " g"
case "Fluid Ounces" => fOzToMl*measure.amount + " mL"
case "Gallons" => galToL*measure.amount + " L"
case "Miles" => milesToKm*measure.amount + " km"
case "Inches" => inchToCm*measure.amount + " cm"
case "Celsius" => (9.0/5.0*measure.amount + 32.0) + " F"
case "Kilograms" => measure.amount/lbToKg + " lbs"
case "Grams" => measure.amount/ozToG + " oz"
case "Millileters" => measure.amount/fOzToMl + " fl. oz."
case "Liters" => measure.amount/galToL + " gallons"
case "Kilometers" => measure.amount/milesToKm + " miles"
case "Centimeters" => measure.amount/inchToCm + " inches"
case _ => ""
}
}
|
Measurement é uma classe de caso. Este é um recurso conveniente do Scala. Decorando uma classe com "caso" faz com que ela tenha um construtor gerado que requer os atributos da classe, assim como implementações do equals,
hashCode, e
toString. Isso é perfeito para classes de estrutura de dados, como Measurement. Isso também gera elementos para os atributos definidos — neste caso, uom e amount. Nós poderíamos ter definido aqueles atributos como vars (variáveis mutáveis), gerando também setters para nós. Aquela única linha do Scala pode fazer muito!
A seguir, UnitsConverter é também um singleton por ser definido usando a palavra chave do objeto. Seu método único é convert. Verifique como convert é definido como sendo igual à uma instrução única — uma instrução correspondente. Isso é uma única expressão, assim não são necessários os parênteses. Isso usa correspondência padrão do Scala. Isso é um poderoso recurso comum em linguagens de programação funcional. É similar à uma instrução switch na linguagem Java e várias outras. No entanto, somos capazes de corresponder contra caracteres (normalmente, a correspondência pode ser mais sofisticada que isso). O caractere é correspondido, o cálculo apropriado é realizado, e um caractere formatado retorna para ser exibido. Finalmente, verifique o último caso que corresponde
_. O underscore é usado como um curinga em vários locais no Scala. Neste caso, ele diz para corresponder qualquer coisa, como se fosse uma instrução padrão na linguagem Java.
Agora que entendemos os cálculos no aplicativo, vamos finalizar o aplicativo olhando o restante da configuração UI e menus.
Vamos voltar ao Listing 4. Nós prometemos olhar para
setUomChoice. Este método é definido como tomando um parâmetro do tipo UnitsSystem. Vamos dar uma olhada em como este tipo é definido.
Lista 7. O
UnitsSystemsealed case class UnitsSystem() case object ENGLISH extends UnitsSystem case object METRIC extends UnitsSystem |
Nós vemos que UnitsSystem é um caso de classe selado sem atributos. Não parece ser muito útil. A seguir, veremos dois objetos de caso. Lembre-se que object determina um singleton no Scala. Neste caso, existem dois objetos de caso e cada amplia UnitsSystem. Este é um idioma comum no Scala para fornecer um caminho mais simples e seguro para fazer enumerações.
Agora a implementação do setUomChoice faz mais sentido. Depois que obtivermos um identificador no spinner, combinaremos que tipo de UnitsSystem foi passado. Isso identifica uma matriz para usar do arrays.xml que vimos anteriormente. É usando o R classe que o Android SDK gera para nós para representar recursos, como o arquivo arrays.xm. Assim que sabemos qual matriz usar, usamos aquela matriz como fonte de dados do spinner através da criação de um adaptador (neste caso, um
ArrayAdapter) que passamos para o spinner.
Finalmente, olhe para onCreateOptionsMenu e
onMenuItemSelected métodos do Listing 4. Estes são métodos definidos no Activity que estamos substituindo em nossa atividade de conversão. O primeiro método cria um menu. O segundo cuida do caso de um usuário selecionando Inglês ou métrica do menu. Isso simplesmente chama o setUomChoice novamente. Isto permite o usuário mudar entre converter de unidades Inglesas para métricas, ou de unidades métricas para Inglesas.
A arquitetura da plataforma do Android se abre para qualquer linguagem de programação que execute a Máquina Virtual Java. Nós vimos como configurar um projeto Android para operar com código do Scala. Este tipo de procedimento pode ser estendido para outras linguagens de programação JVM, como Groovy, JRuby ou Fan. Com a linguagem de programação Scala à nossa disposição, se torna ainda mais fácil escrever aplicativos Android. Você ainda é capaz de desenvolver usando Eclipse. Depurando ambas com o emulador e um dispositivo funciona diretamente do Eclipse. Você obtém todas as ferramentas e uma linguagem de programação mais produtiva.
| Descrição | Nome | Tamanho | Método de download |
|---|---|---|---|
| Converter sample | os-eclipse-scala-Converter.zip | 2KB | HTTP |
Informações sobre métodos de download
Aprender
-
Leia o tutorial "Develop Android applications with Eclipse" .
-
Leia Sccala and Android in an Eclipse project para uma introdução de como construir projetos Scala Eclipse para Android.
-
Consulte "The busy Java developer's guide to Scala" para um tour compreensivo do Scala.
-
Scala pode fazer várias tarefas comuns de programação mais fáceis, assim como trabalhar com XML. Veja como em "Scala and XML."
-
Scala potencializa Lift, uma inovadora estrutura de aplicativo de Web. Obtenha uma introdução ao Lift em "Give Apache Geronimo a Lift."
-
Leia "Build Comet applications using Scala, Lift, jQuery" para ver como Lift pode permitir desenvolvimento avançado de Ajax e Comet.
-
Para as últimas informações sobre Scala, consulte
o Scala Web site.
-
Obtenha a Android SDK documentation.
-
Consulte The Open Handset
Alliance, patrocinador do Android.
-
Leia Unlocking
Android, um ótimo recurso para aprender Android.
-
Consulte o "Recommended Eclipse reading list."
-
Navegue por todo o conteúdo do Eclipse no developerWorks.
-
Siga o developerWorks no Twitter.
-
Novo no Eclipse? Leia o artigo do developerWorks "Get started with the Eclipse Platform" para aprender sobre sua origem e arquitetura, e como estender Eclipse com plug-ins.
-
Expanda suas habilidades consultando o recurso de projetos Eclipse do IBM developerWorks.
-
Para ouvir entrevistas e discussões interessantes para desenvolvedores de software, consulte o developerWorks podcasts.
-
Mantenha-se atualizado com o webcast e eventos técnicos do developerWorks.
-
Assista e aprenda sobre a IBM e as tecnologias e funções de produtos de código aberto com as demos do developerWorks On demand grátis.
-
Verifique as próximas conferências, feiras, webcasts e outros Eventos em todo o mundo que sejam de interesse dos desenvolvedores IBM de código aberto.
-
Visite a área do Open source do developerWorks para informações extensivas sobre como fazer, ferramentas e atualizações de projetos que o ajudarão a desenvolver com as tecnologias de código aberto e usá-las com os produtos IBM.
Obter produtos e tecnologias
-
Faça o Download do Android SDK,
acesse a referência API, e obtenha as últimas informações sobre o Android no
site oficial dos desenvolvedores do Androide.
-
Android é fonte aberta, o que significa que você pode obter o código da fonte para isso do
Android Open Source Project.
-
Consulte os últimos downloads do Eclipse technology na IBM alphaWorks.
-
Faça o Download de Eclipse Platform e outros projetos do Eclipse Foundation.
- Faça download
das versões de avaliação de produtos IBM
ou explore as avaliações on-line no
IBM SOA Sandbox e utilize as ferramentas de desenvolvimento de aplicativos e produtos de middleware do
DB2®, Lotus®, Rational®, Tivoli® e WebSphere®.
-
Inove em seu próximo projeto de desenvolvimento em código aberto com o software de teste da IBM, disponível para download ou em DVD.
Discutir
-
O grupo de discussão do Eclipse Platform deve ser a sua primeira para discutir questões sobre Eclipse. (Selecionando este ativará o seu aplicativo leitor de notícias Usenet padrão e abrir eclipse.platform.)
-
O grupo e discussão do Eclipse possui vários recursos para pessoas interessadas em usar estender Eclipse.
-
Participe dos developerWorks blogs e envolva-se na comunidade do developerWorks.
