27 de out. de 2017

JavaFX com Bean Validation e CDI 2.0

Quando JavaFX 2 foi lançado me lembro de muitas pessoas procurando por frameworks que facilitariam a integração de JavaFX com frameworks de validação, um container de injeção de independência e outros serviços enteprise. Atualmente pode integrar JavaFX com Bean Validation e CDI 2.0 sem o uso de qualquer framework externo. Nesse artigo eu vou mostrar como validar os campos de sua aplicação JavaFX usando Bean Validation  e uma aplicação com CDI.

Antes de falar sobre essas coisas eu recomendo que você cheque os seguintes artigos:


Validando Controls do JavaFX usando Bean Validation



Com Bean Validation 2.0 temos o "unwrap" automático de propriedades JavaFX. Isso significa que se você tiver um campo do tipo ObservableValue o validator vai conseguir tirar o valor real e aplicar a validação. No entanto, os campos de control do JavaFX ainda não são suportados, então se você tentar validar os campos diretamente você vai ter uma exceção:  


Isso acontece por uqe a implementação do validator não sabe como retirar valores de um controller JavaFX. O que precisamos é criar uma classe que implementa javax.validation.valueextraction.ValueExtractor. Precisamos também usar o tipo do controle que essa classe vai saber tirar o valor (usar genéricos para isso), criar o método que faz a atual extração do valor (tipo um getText em um TextField) e, por fim, usar a anotação  javax.validation.valueextraction.ExtractedValue para configurar o tipo que é tirado do control(String, Integer, LocalDate...). Para evitar passos adicionais usamos a anotação  javax.validation.valueextraction.UnwrapByDefault na nossa implementação. As nossas classes para tirar valores do DatePicker e do TextField estão abaixo:

Value Extractor para o DatePicker

Value Extractor para o TextField

Finalmente, precisamos registrar isso com o framework de validação. Há 
Finally we must register it in within the bean validation framework. There are a couple of algumas formas de fazer isso, escolhemos o modo com SPI  que é criar um arquivo com o nome META-INF/services/javax.validation.valueextraction.ValueExtractor com o nome das classes dos nossos ValueExtractors:

Conteúdo do arquivo META-INF/services/javax.validation.valueextraction.ValueExtractor


Poderíamos ter uma extensão de bean validation para os controles do JavaFX, assim não teríamos que criar novamente. Se você sabe de algum projeto assim me fale que eu menciono aqui!

Mostrar os erros de validation para os usuários


Você provavelmente quer mostrar os erros para os usuários quando os valores que ele entrou não são válidos. Uma vez que você tenha acesso a classe Validator no controller você simplesmente pode chamar o método validate no controller  e mostrar aos usuários os erros de validação em um label, por exemplo. Eu pessoalmente não gosto dessa solução pois eu acho que é muito intrusivo já que você terá que modificar a view para incluir mais labels só para a validação. Uma solução mais simples é mostrar um tooltip com o erro de validação no campo que deu problema. Poderíamos também mostrar um dialog ou bordar o campo de vermelho, mas só mostramos o tooltip mesmo:

Método showValidationErrors pega o controle que tem erro de validação adiciona um tooltip e mostra

Para mostrar o tooltip precisamos acessar o controle em sí e para isso precisamos fazer um pouco de reflexão por isso fomos ao monte meditar , por isso o campo usado deve ter o modificador público ou ter métodos de acesso a ele.

CDI, Bean Validation e JavaFX


Não há nada para adicionar sobre CDI aqui do que eu já mencionei no post Using CDI 2.0 in a JavaFX application. Eu gostaria de compartilhar que CDI e Bean Validation funcionam em uma aplicação JavaFX e por isso eu posso simplesmente injetar o validator na minha classe!Uma única dependência vai tornar isso possível: org.hibernate.validator:hibernate-validator-cdi. Veja abaixo todos os arquivos da aplicação:

Our maven project and its files


Essas são as dependências que eu adicionei ao pom.xml:

These are all dependencies required to use bean validation and CDI on a JavaFX application

O código de uma aplicação de exemplo pode ser encontrado no meu github github. Abaixo você pode ver como a validação usando tooltips funciona na aplicação:



Por quey isso é importante?


Estar apto a integrar JavaFX com JavaEE (ou devo dizer EE4J já?) é algo chave para quem quer criar aplicação reais com JavaFX, isso trás persistência, validação, injeção de dependência e mais! it will bring persistence, validation, injection and more. A principal questão é: será que isso funcionaria em uma aplicação Android que usa Gluon/ JavaFX Ports?

Para mais exemplos de validação você pode ver essa pull request do Hendrik Ebbers  para o projeto hibernate validator.

25 de out. de 2017

Usando CDI 2.0 em uma aplicação JavaFX

Com o lançamento do CDI 2.0 nós temos um container que pode ser usado em uma aplicação Java SE! Anteriorment se você quisesse fazer uso de CDI em uma aplicação Java SE você teria que usar classes proprietárias do Weld, veja o artigo FXML & JavaFX—Fueled by CDI & JBoss Weld.

Essa feature é bem explicada pelo Adam Bien em um vídeo, veja:


Claro que era meu objetivo tentar isso em uma aplicação javaFX e nesse artigo breve compartilharei com vocês a minha experiência e o código.

Crie o container em uma aplicação JavaFX


Uma aplicação JavaFX é uma classe que estende de javafx.application.Application, que é o ponto de entrada para aplicações JavaFX e onde o stage principal pode ser usado. Para usar CDI devemos criar o SeContainer e certificar-se que ele irá criar todas as classes gerenciadas pelo CDI. Isso deve ser feito no ponto de entrada da Application para que possamos gerenciar todas as classes criadas de lá.

Uma vez criado o container você tem diferente formas de enviar o Stage para a aplicação JavaFX (a que você vai programar e não a que criamos para grudar CDI ao javaFX). Por exemplo, você pode fazer com que users possam estender uma interface e então injetar a classe do usuário dentro da nossa CDI application para invocar por lá.



Hpa uma forma mais elegante que é usando a API de observer do CDI. Nesse caso podemos criar uma anotação que será o qualifier para o evento  javafx.stage.Stage. CDI sabe inteligentemente as classes que observam aquele evento e vai selecionar a classe quando ativarmos o evento com o stage principal. Isso é semelhante ao que você pode ver no artigo que mencionamos FXML & JavaFX—Fueled by CDI & JBoss Weld.



Independente da solução que escolher não "desligue o container" ou isso pode deixar app inconsistente. Na verdade isso não deve afetar muito, mas eu decidi não fazer o shutdown.

Beleza! Daqui pra frente você pode usar CDI na sua app! Vá em frente e injete coisas nas suas classes JavaFX. Exceto, é claro, se você estiver usando FXML. Nesse caso JavaFX cria os controllers para você usando reflection e CDI não estará ciente da classe criada, ou seja, vai tudo ser nulo pois CDI não injetará. Uma solução é criar um FXMLLoader e fazer o CDI criar os controllers invés de deixar o FXML loader criar ele. O código abaixo foi novamente pego de  FXML & JavaFX—Fueled by CDI & JBoss Weld mas com a adição do delicioso lambda do Java 8:






Finalmente você pode criar sua própria aplicação mas NUNCA chame os métodos estáticos do FXMLLoader, use o injetado, por exemplo:



Lembre que você agora pode injetar coisas no seu controller. No nosso caso eu injetei um clássico Greeter, semelhante ao que abordo no post Primeiros Passos com CDI:





Não se esqueça também  do arquivo vazio beans.xml que irá ativar CDI. Essa é a estrutura do projeto que eu usei nos meus testes:




O resultado final foi mostrado abaixo - a diferença é que a mensagem veio de um bean gerenciado por FXML:


O código completo pode ser encontrado no github.