sábado, 29 de março de 2014

REST - Básico sobre node.js

Algumas pessoas me escreveram pedindo algumas aulas básicas sobre o node.js, então vamos começar uma pequena série sobre este software. Devemos primeiro saber que para aprender o node.js é necessário possuirmos um bom conhecimento de JavaScript, então considerarei esse como um pré-requisito.

Instalação do node.js


No site oficial do node.js podemos baixar gratuitamente a última versão disponível, como é um arquivo executável basta seguir as instruções do instalador, ao término abra uma janela de comando e digite:
node -v
Se a resposta for o número da versão corrente, então o node.js foi instalado sem problemas.

Criação do Servidor


Como vimos nas aulas práticas de REST, uma das funções primárias do node.js é a criação de um Servidor Web, este tipo de servidor simplesmente fica "escutando" em uma determinada porta alguma solicitação realizada - passo conhecido como REQUEST - e ao recebê-la busca fornecer a resposta  adequada - passo conhecido como RESPONSE.

Com simples comandos podemos criar um Servidor Web com o node.js, basta realizarmos três passos:
  1. Criar uma variável que representa o módulo http.
  2. Definir o que este servidor fará.
  3. Ativar o servidor em alguma porta.
Vamos então criar uma pasta (utilize nomes de pastas e subpastas simples, sem espaços ou acentuação) chamada servNode e dentro desta crie um arquivo chamado server.js com a seguinte codificação:
var http = require('http');

var server = http.createServer(function(req, res) {
  res.writeHead(200, {"Content-Type": "text/html; charset=UTF-8"});
  res.write("<h1>Meu primeiro teste com NodeJS!</h1>");
  res.end();
});

server.listen(3000, function() {
  console.log("Servidor do NodeJS ativo...");
});
Na janela de comandos acesse esta pasta e digite:
node server.js
Como resposta deve mostrar que o servidor está ativo. Observe que o código está dividido em 3 partes, cada uma representa um passo:
  1. Para um módulo qualquer do node.js usamos o método require() e criamos uma variável que recebe um objeto que representa o módulo chamado. 
  2. Um servidor é criado a partir do método createServer que como parâmetro define uma função com duas variáveis que representa objetos das ações request (objeto req) e response (objeto res). Através do objeto de response podemos definir o que desejamos transmitir como resposta, então quando o servidor estiver funcionando enviaremos uma resposta HTML com uma mensagem. O método end() finaliza, empacota e envia a resposta para o cliente. 
  3. Por fim ativamos o servido com o método listen() e informamos qual porta o servidor escutará nossas ações (neste caso na 3000).
Em um navegador digite o endereço: http://localhost:3000, e veremos a seguinte tela:


Na próxima aula aprenderemos como ler e criar arquivos com o node.js.

Obrigado e até a próxima
Fernando Anselmo



sábado, 8 de março de 2014

Android - Projeto 51 - Faltam (2ª Parte)

Na postagem passada vimos a criação da janela principal do projeto com uma primeira montagem. Nesta faremos algumas adaptações para concluirmos o projeto.

Camada das Funções

A primeira camada que vamos criar corresponde as funções que necessitamos. No caso deste projeto, funções de tratamento com Datas. Criamos uma nova classe no pacote projeto.livro.faltam chamada UtilData com a seguinte codificação:
public class UtilData {

  private static Date dataAtual = new Date();

  private static String formatarData(final Date data) {
    DateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.UK);
    return sdf.format(data);
  }

  private static Date strToDate(final String dataStr) {
    DateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.UK);
    try {
      return sdf.parse(dataStr);
    } catch (Exception e) {
      Log.e("Faltam", "Erro na data: " + e.getMessage());
    }
    return null;
  }

  public static Date convDiaMesToDate(final String diaMes) {
    Calendar original = new GregorianCalendar();
    original.setTime(dataAtual);
    int anoAtual = original.get(Calendar.YEAR);
    String montaData = diaMes.substring(0, 2) + "/" + diaMes.substring(2) + "/"
        + anoAtual;
    return strToDate(montaData);
  }

  public static Date convDiaToDate(final String dia) {
    Calendar original = new GregorianCalendar();
    original.setTime(dataAtual);
    int diaAtual = original.get(Calendar.DAY_OF_MONTH);
    int mesAtual = original.get(Calendar.MONTH);
    int anoAtual = original.get(Calendar.YEAR);
    if (diaAtual > Integer.parseInt(dia)) {
      mesAtual++;
      if (mesAtual > 11) {
        mesAtual = 0;
        anoAtual++;
      }
    }
    mesAtual++;
    String montaData = dia + "/"
        + ((mesAtual < 10) ? "0" + mesAtual : mesAtual) + "/" + anoAtual;
    return strToDate(montaData);
  }

  public static String retornarDiferencaComAtual(final Date dataOri) {
    // Data é hoje
    if (formatarData(dataAtual).equals(formatarData(dataOri))) {
      return "É hoje";
    }
    Calendar diaCirc = new GregorianCalendar();
    diaCirc.setTime(dataAtual);
    int dia = 0;
    while (!formatarData(diaCirc.getTime()).equals(formatarData(dataOri))) {
      diaCirc.add(Calendar.DAY_OF_MONTH, 1);
      dia++;
    }
    return dia + (dia > 2 ? " dias" : " dia");
  }
}
Os métodos desta classe são:
  • formatarData(final Date data): converter um objeto do tipo Date para um objeto tipo String no formato DD/MM/AAAA.
  • strToDate(final String dataStr): converter um objeto tipo String no formato DD/MM/AAAA para um objeto tipo Date.
  • convDiaMesToDate(final String diaMes): converter um objeto tipo String no formato DDMM (Dia e Mês) para um objeto tipo Date.
  • convDiaToDate(final String dia): converter um objeto tipo String no formato DD (Dia) para um objeto tipo Date. Se o dia informado for menor que o dia atual o mês será o atual, caso contrário o mês será o seguinte.
  • retornarDiferencaComAtual(final Date dataOri): retornar quantos dias faltam para chegar até o dia atual, recomenda-se que este método seja utilizado somente para períodos de um ano.
Java trata objetos tipo Date com duas classes: Calendar e GregorianCalendar. Se observamos veremos que todos os métodos contidos na própria Date estão depreciados, então usamos basicamente essas duas classes para lidar com esse tipo de objeto. Já as conversões para o tipo String são tratadas pela classe: SimpleDateFormat.

Camada de Dados

Precisamos realizar uma pequena modificação no método toString() da classe Registro, modificá-lo para a seguinte codificação:
  @Override
  public String toString() {
    if (dia.length() == 2) {
      return descricao + ": "
          + UtilData.retornarDiferencaComAtual(UtilData.convDiaToDate(dia));
    } else {
      return descricao + ": "
          + UtilData.retornarDiferencaComAtual(UtilData.convDiaMesToDate(dia));
    }
  }
Agora a diferença de dias já pode ser visualizada sem problemas na lista.

Uma boa prática é separar os métodos de Gravação e Leitura de arquivo e isolá-los em uma nova classe no pacote projeto.livro.faltam chamada UtilArquivo com a seguinte codificação:
public class UtilArquivo {

  protected static void carregarLista(final List<registro> lista,
      final FileInputStream arquivo) throws Exception {
    ObjectInputStream ois = null;
    try {
      ois = new ObjectInputStream(arquivo);
      Registro reg;
      do {
        reg = (Registro) ois.readObject();
        lista.add(reg);
      } while (true);
    } catch (EOFException e) {
    } finally {
      ois.close();
    }
  }

  protected static void gravarLista(final List<registro> lista, 
      final FileOutputStream arquivo) throws Exception {
    ObjectOutputStream oos = null;
    try {
      oos = new ObjectOutputStream(arquivo);
      for (Registro regs : lista) {
        oos.writeObject(regs);
        oos.flush();
      }
    } finally {
      oos.close();
    }
  }
}
São os mesmos métodos que já vimos na postagem anterior. Isolados aqui, foram mais simplificados, ou seja, o método carregarLista() carrega todos os objetos do arquivo para a lista, enquanto que o método gravarLista() grava todos os objetos da lista para o arquivo.

Atividade Principal

Para finalizarmos nosso aplicativo, procederemos mudanças na classe MainActivity:
public class MainActivity extends ListActivity {

  private EditText edDescricao;
  private EditText edDia;
  private final List<registro> lista = new ArrayList<registro>();
  private ListView listaRegs;

  private void eliminarRegistro() {
    for (int i = listaRegs.getCount(); i > -1; i--) {
      if (listaRegs.isItemChecked(i)) {
        lista.remove(i);
      }
    }
    mostrarLista();
  }

  private void guardarRegistro() {
    lista.add(new Registro(edDescricao.getText().toString(), edDia.getText()
        .toString()));
    mostrarLista();
    edDescricao.setText("");
    edDia.setText("");
    edDescricao.requestFocus();
  }

  private void mostrarLista() {
    ArrayAdapter adaptador = new ArrayAdapter(this,
        android.R.layout.simple_list_item_checked, lista);
    listaRegs.setAdapter(adaptador);
  }

  @Override
  protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    edDescricao = (EditText) findViewById(R.id.descricao);
    edDia = (EditText) findViewById(R.id.dia);
    listaRegs = getListView();
    listaRegs.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    findViewById(R.id.guardar).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(final View v) {
        guardarRegistro();
      }
    });
    findViewById(R.id.eliminar).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(final View v) {
        eliminarRegistro();
      }
    });
  }

  @Override
  protected void onStart() {
    super.onStart();
    try {
      UtilArquivo.carregarLista(lista, openFileInput("dias.reg"));
    } catch (Exception e) {
      Log.e("Faltam", "Erro Leitura: " + e.getMessage());
    }
    mostrarLista();
  }

  @Override
  protected void onStop() {
    super.onStop();
    try {
      UtilArquivo.gravarLista(lista,
          openFileOutput("dias.reg", Context.MODE_PRIVATE));
    } catch (Exception e) {
      Log.e("Faltam", "Erro Leitura: " + e.getMessage());
    }
  }
}
Vamos começar pelo método onCreate() que é chamado assim que o aplicativo é iniciado, a lista da tela é associada a um objeto do tipo ListView, sua visão também foi modificada permitindo que o usuário possa selecionar os registros que deseja excluir. Através do ciclo de vida o próximo método chamado é o onStart() que será responsável por carregar a lista do arquivo texto, e esta lista agora só será gravada quando o método onStop() for chamado (assim evitamos I/O desnecessários). O método mostrarLista() procede a atualização da lista na tela (devemos lembrar que estamos trabalhando com dois objetos o ListView e um List que não estão sincronizados).

Finalmente os dois métodos responsáveis pelas ações de Gravar e Eliminar os registros da lista através do pressionamento dos botões pelo usuário. O método guardarRegistro() adiciona um novo registro a lista e o método eliminarRegistro() verifica qual registro da lista foi selecionado pelo usuário e procede sua retirada (observe que o laço anda de trás para frente eliminando os registros, isso é feito pois tudo é posicional).


Pronto, podemos rodar sem problemas nosso projeto e inclusive passá-lo para o celular (não esqueça da diretiva android:installLocation=”auto” que permitirá que a aplicação seja colocada no cartão de memória.


Utilize este projeto para descobrir como:
  • Realizar gravações de objetos em arquivos;
  • Realizar a leitura de objetos em arquivos;
  • Utilizar o objeto tipo Date e suas conversões para o tipo String;
  • Utilizar lista com marcadores tipo Check;
  • Incluir e eliminar dinamicamente registros em listas.
Obrigado e até a próxima
Fernando Anselmo

terça-feira, 4 de março de 2014

Android - Além dos 50 Projetos

Pode-se dizer que meu livro de maior sucesso chama-se "Android em 50 Projetos", este livro ocupa o primeiro da lista dos mais vendidos do site da VisualBooks a um bom tempo.


Gosto muito desse livro principalmente por ser muito prático, a teoria foi embutida dentro dos projetos, assim os leitores podem aprender criando. Obviamente que o período de criação não para então vou aproveitar este blog para dar continuação a mais alguns projetos e tentar passar mais algumas teorias que não foram vistas no livro.

Não, você não precisa comprar o livro para usar estes projetos. Basta ir no meu site e na seção do Curso Android e baixar gratuitamente os 8 tutoriais introdutórios.

Ficha do Projeto 51

Nome do Projeto: Faltam
Plataforma: Android 2.3.3 (Honeycomb)
Nome do Pacote: projeto.livro.faltam
Nome da Atividade: MainActivity
Versão mínima do SDK: 10
Uma grande preocupação que sempre tive foi de me lembrar de datas importantes, por exemplo, Quanto falta para o meu aniversário de casamento? Quanto falta para o aniversário da minha esposa? E do meu filho? Outras também gostaria de saber, quanto falta para o Natal por exemplo. Mas também posso usá-lo para pagar as contas mensais. Então a ideia deste aplicativo é inserir uma descrição e sua pseudo data, com DD ou DD/MM e com base na data atual ser informado quantos dias faltam para chegar aquela pseudo data.

Camada de Visão

Vamos começar trabalhando com as Strings que iremos utilizar, então modifique o arquivo Strings.xml contido na pasta res/values:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Faltam</string>
    <string name="action_settings">Faltam</string>
    <string name="lbDescricao">Descrição do evento:</string>
    <string name="htDescricao">Informe um descritivo do evento</string>
    <string name="lbDia">Dia ou Dia/Mês:</string>
    <string name="htDia">Informe DD ou DDMM</string>
    <string name="btGuardar">Guardar</string>
    <string name="btEliminar">Eliminar</string>
</resources>
Próximo passo é acertarmos o layout principal, para isso altere o arquivo content_main.xml contido na pasta res/layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/lbDescricao" />
    <EditText
        android:id="@+id/descricao"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/htDescricao" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/lbDia" />
    <EditText
        android:id="@+id/dia"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:hint="@string/htDia" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/guardar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btGuardar" />
        <Button
            android:id="@+id/eliminar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btEliminar" />
    </LinearLayout>
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
Na primeira parte teremos dois campos para a entrada dos dados (descrição e dia), os botões separam esta primeira parte da listagem contendo os eventos já cadastrados.

Classe de Registro

Sempre que trabalhamos com listas é ideal criar uma classe que representa um registro desta, e isso facilitará nosso trabalho posterior de armazenar este registro. No pacote projeto.livro.faltam crie uma nova classe chamada Registro com a seguinte codificação:
public class Registro implements Serializable {

  private static final long serialVersionUID = 1L;
  private final String descricao;
  private final String dia;

  public Registro(final String descricao, final String dia) {
    this.descricao = descricao;
    this.dia = dia;
  }

  @Override
  public String toString() {
    return descricao + " | " + dia;
  }
}
Esta classe implementa a interface Serializable pois seus objetos serão armazenados. Nesta temos dois campos descrição e dia do tipo String. Para evitar a criação de métodos set/get usamos o construtor da classe e modificamos o método toString (que em breve receberá novos códigos).

Atividade Principal

Na classe MainActivity.java deixe-a com a seguinte codificação:
public class MainActivity extends ListActivity {

  private EditText edDescricao;
  private EditText edDia;
  private List lista = null;

  private void carregarDados() {
    lista = new ArrayList();
    ObjectInputStream ois = null;
    try {
      ois = new ObjectInputStream(openFileInput("dias.reg"));
      Registro reg;
      do {
        reg = (Registro)ois.readObject();
        lista.add(reg);
      } while (true);
    } catch (EOFException e) {
    } catch (Exception e) {
      Log.e("Faltam", "Erro Leitura: " + e.getMessage());
    } finally {
      try {
        ois.close();
      } catch (Exception e) {
        Log.e("Faltam", "Erro ao Fechar Arquivo: " + e.getMessage());
      }
    }
    if (lista != null) {
      ArrayAdapter adaptador =
          new ArrayAdapter(this, 
          android.R.layout.simple_expandable_list_item_1, lista);
      setListAdapter(adaptador);
    }
  }

  private void guardarRegistro() {
    Registro reg = new Registro(
      edDescricao.getText().toString(), edDia.getText().toString());
    ObjectOutputStream oos = null;
    try {
      oos = new ObjectOutputStream(
        openFileOutput("dias.reg", Context.MODE_PRIVATE));
      for (Registro regs : lista) {
        oos.writeObject(regs);
        oos.flush();
      }
      oos.writeObject(reg);
      oos.flush();
    } catch (Exception e) {
      Log.e("Faltam", "Erro Gravação: " + e.getMessage());
    } finally {
      try {
        oos.close();
      } catch (IOException e) {
        Log.e("Faltam", "Erro ao Fechar Arquivo: " + e.getMessage());
      }
    }
    edDescricao.setText("");
    edDia.setText("");
    edDescricao.requestFocus();
    carregarDados();
  }

  @Override
  protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    edDescricao = (EditText)findViewById(R.id.descricao);
    edDia = (EditText)findViewById(R.id.dia);
    findViewById(R.id.guardar).setOnClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(final View v) {
            guardarRegistro();
          }
        });
    carregarDados();
  }
}
Como o layout desta classe possui uma lista, então passa a ser herança de ListActivity. O método carregarDados obtém os dados a partir de um arquivo texto que foi armazenado com os objetos de registro, e são lidos através de um ObjectInputStream, que são adicionados em uma Lista desses objetos, o último passo é criar um ArrayAdapter para carregar essa lista na tela. O método guardarRegistro obtém os dados que foram digitados nos campos EditText e os armazena (como objeto do tipo registro) no arquivo texto.

Existem alguns problemas quanto a leitura e a armazenagem de objetos. Quanto a leitura é impossível saber quando terminou, portanto repare que o laço de leitura só termina quando acontece um EOFException. Já na gravação o certo seria usar um tipo Context.MODE_APPEND no método openFileInput só que isso simplesmente não funciona, resultado, toda vez devemos gravar os dados antigos e o novo registro.

No método onCreate amarramos nossos EditText de tela com dois objetos da classe, armamos o evento onClick para o botão guardar um novo registro e carregamos os dados já armazenados.

E a primeira parte do nosso projeto está pronta o que resultará na seguinte tela:


Ainda está meio esquisito a listagem que não mostra os dias faltantes para o evento e também não é possível eliminar qualquer registro. Em breve, veremos como realizar essas atividades.

Obrigado e até a próxima
Fernando Anselmo