Criando um Arquivo de Log

Uma boa solução para descobrir bugs no software quando o software está em fase de teste ou até mesmo instalado é criando um arquivo texto de log, que pode ser armazenado no diretório da própria aplicação. Um cuidado que observei na criação de um arquivo de log é o método que utilizamos para criar o arquivo.
Inicialmente utilizei a classe File e o método Create do namespace System.IO:
         // Verifica se o arquivo de log da aplicação já existe
         if (!File.Exists("log_aplicacao.txt"))
         {
             File.Create("log_aplicacao.txt");
         }
                  
O código acima funciona sem problemas desde que o software execute várias outras operações antes de tentar escrever no arquivo de log. Se a próxima operação for por exemplo gravar a iniciação do software logo após criar o arquivo, o software apresentará uma exceção com uma mensagem parecida com “O processo não pode acessar o arquivo log.txt, porque ele está sendo usado por outro processo”.
Esta exceção ocorre pois enquanto o arquivo está sendo criado  o objeto FileStream criado por este método tem um valor default (FileShare de None), ou seja, nenhum outro processo ou código pode acessar o arquivo criado até que o handle do arquivo original seja fechado.

Uma possível solução foi utilizar a classe FileStream e passar o parâmetro FileShare como ReadWrite.
         // Cria o arquivo de log da aplicação
         FileStream fs = new FileStream("log_aplicacao.txt", FileMode.Create,
             FileAccess.ReadWrite, FileShare.ReadWrite);
         fs.Close();
Desde modo logo após a criação do arquivo já é possível escrever no arquivo de log. Segue um exemplo para gravar no log da aplicação a data e hora em que a aplicação foi iniciada.
        static void Main(string[] args)
        {
            try
            {
                 // Verifica se o arquivo de log da aplicação já existe
                if (!File.Exists("log_aplicacao.txt"))
                {
                    // Cria o arquivo de log da aplicação
                    FileStream fs = new FileStream("log_aplicacao.txt", FileMode.Create,
                        FileAccess.ReadWrite, FileShare.ReadWrite);
                    fs.Close();
                }
                // Cria o objeto StreamWriter, passando como parâmetro o nome do
                // arquivo criado anteriormente e o valor booleano true
                // indicando que as informações devem ser acrescentadas no arquivo
                // toda vez que a aplicação for iniciada criando desta forma um
                // arquivo de log com um histório de iniciação da aplicação.
                using (StreamWriter sw = new StreamWriter("log_aplicacao.txt", true))
                {
                    sw.WriteLine("A aplicação iniciada em " + DateTime.Now.ToString());
                    sw.Flush();
                    sw.Close();
                }
                Console.WriteLine("O arquivo foi gerado com suceso!");
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }

Criando uma Assinatura Digital

Os algoritimos utilizados em assinatura digital mapeiam valores de tamanho arbitrário para valores binários de tamanho fixo. O .NET framework possui várias classes que implementam algorítimos de assinatura digital, tais como: 
  • HMACSHA1
  • MACTripleDES
  • MD5CryptoServiceProvider
  • RIPEMD160
  • SHA1Managed
  • SHA256Managed
  • SHA384Managed
  • SHA512Managed 
Quando utilizamos uma classe que implementa uma assinatura digital temos a garantia de que dois conjuntos de valores idênticos terão o mesmo resultado com tamanho fixo. Qualquer pequena alteração no valor fará com que o resultado seja totalmente diferente. 

Segue um exemplo de utilização de uma classe que pode ser utilizada para assinatura digital. O exemplo utiliza a classe SHA256Managed que cria uma assinatura digital para a palavra Romar. 
Para que o exemplo funcione é necessário incluir o namespace System.Security.Cryptography. 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
    class Program
    {
        static void Main(string[] args)
        {
            byte[] dados = Encoding.ASCII.GetBytes("Romar");
            byte[] resultado;
            // Cria um hash para a palavra Romar
            SHA256 shaM = new SHA256Managed();
            resultado = shaM.ComputeHash(dados);
            // Utilizando a classe Encoding
            string palavraCriptografada1 = Encoding.ASCII.GetString(resultado);
            Console.WriteLine("Resultado com a classe Encoding:");
            Console.WriteLine(palavraCriptografada1);
            Console.WriteLine();
            // utilizando a classe BitConverter
            string palavraCriptografada2 = BitConverter.ToString(resultado);
            Console.WriteLine("Resultado com a classe BitConverter:");
            Console.WriteLine(palavraCriptografada2);
            Console.ReadKey();
        }
    }
No exemplo imprimimos o resultado de duas formas; uma exibindo o resultado como uma string utilizando a classe Encoding e a outra utilizando a classe BitConverter. A classe Encoding foi utilizada com o valor ASCII, portanto o resultado é uma string com os valores da assinatura digital exibidos em caracteres que nem sempre são imprimíveis e podem apresentar dificuldade para se armazenar em alguns tipos de arquivos. Uma opção para armazenamento seria trazer o resultado em um formato mais amigável, por exemplo em hexadecimal. A classe BitConverter facilita este trabalho fazendo a conversão do resultado em um array de bytes que pode ser facilmente manipulado e armazenado.

 
Resultado com a classe Encoding:
k?+??$???►?,?4F
?↨B¶v?e☺\???C 




Observando o resultado do exemplo utilizando-se a classe BitConverter podemos observar que temos 32 bytes. A classe SHA256Managed sempre retornará um tamanho fixo de 32 bytes (256 bits).

Resultado com a classe BitConverter:
6B-EC-2B-AE-B9-24-E2-F9-BC-10-D1-2C-D9-34-46-07-0A-87-17-42-14-76-EA-5B-08-65-01
-5C-BD-F1-D5-43

SelectAll no Controle MaskedTextBox

Recentemente resolvi utilizar um controle MaskedTextBox em uma aplicação Windows Forms, para preencher um campo de data e utilizar a facilidade da máscara de data.

Para facilitar para o usuário, quando o formulário era apresentado na aplicação o campo de data já aparecia preenchido com a data atual. Bastava que o usuário aceitasse a data atual ou se quisesse alterar a data, o campo já apareceria selecionado facilitando assim a inserção da nova data.

form1

Para fazer com que a informação no campo de data apareça selecionada quando o foco está no campo de data normalmente utilizamos o método SelectAll() do controle. No caso do controle MaskedTextBox este método não funciona diretamente como no controle TextBox. Encontrei duas formas de conseguir selecionar o texto como indicado na figura acima:

A primeira forma é através de delegate, chamando o método SelectAll, depois que todas as mensagens do formulário são processadas.

        private void mskDataPublicacao_Enter(object sender, EventArgs e)
        {
            this.BeginInvoke((MethodInvoker)delegate()
            {
                this.mskDataPublicacao.SelectAll();
            });
        }
A segunda forma é chamando o método Focus() antes do método SelectAll().

        private void mskDataPublicacao_Enter(object sender, EventArgs e)
        {
            this.mskDataPublicacao.Focus();
            this.mskDataPublicacao.SelectAll();
        }

Teste e Debug de WebService em Máquina Remota

Durante o desenvolvimento de WebService o Visual Studio facilita o teste e debug na própria Interface de desenvolvimento, mas esta facilidade funciona somente quando estamos fazendo o teste na máquina de desenvolvimento utilizando o http://localhost.

Quando por algum motivo precisamos fazer um teste ou simplesmente verificar se um WebService esta respondendo em uma máquina remota não é possível utilizar o browser com a URL do WebService, pois por questões de segurança os métodos e funções do webService não estão acessíveis na máquina remota.

Podemos fazer uma alteração no arquivo Web.Config do WebService para liberar o acesso aos métodos e funções do WebService em um browser.

Para liberar o acesso para teste dos métodos e funções do WebService basta incluir o HttpGet e HttpPost na tag <system.web> do WebService.

    <system.web>
      <webServices>
        <protocols>
          <add name="HttpPost" />
          <add name="HttpGet" />
        </protocols>
      </webServices>
    </system.web>

Depois de executado todos os testes necessários altere a configuração para remover o acesso ao HttpPost e HttpGet.

    <system.web>
      <webServices>
        <protocols>
          <remove name="HttpPost" />
          <remove name="HttpGet" />
        </protocols>
      </webServices>
    </system.web>

Não esquecer de desabilitar o acesso ao HttpPost e HttpGet depois dos testes. Isto é muito importante por questões de segurança.

Gerar Script SQL para criar Tabelas com Dados

Na época da base de dados Northwind era comum baixarmos o script da base de dados já com os dados aos invés de um arquivo para restaurar no nosso SQL Server.

Normalmente esta geração de script era feita utilizando-se outras ferramentas. A partir do SQL Server 2008 podemos gerar um script que quando executado cria as tabelas e preenche os dados.
Para gerar este script é bem simples. Vou demonstrar como gerar um script para criar as tabelas já com os dados da base AdventureWorksLT2008. 

Com o botão direito do mouse selecione a base AdventureWorksLT2008 e clique no menu TASKS – GENERATE SCRIPTS.
SQL1
Na tela SELECT DATABASE selecione a base de dados AdventureWorksLT2008 e habilite o campo Script all objects in the selected database. Clique no botão NEXT.
SQL2
Na tela CHOOSE SCRIPT OPTIONS vá até a opção Table/View Options e altere o campo Script Data para True. Clique no botão NEXT.
SQL3
Na tela OUTPUT OPTION selecione onde o script deve ser gerado. O script pode ser gerado em um arquivo, na Área de Transferência ou em uma nova janela de consulta no SQL Management Studio. Selecione a opção Script to New Query Window para visualizar o script. Clique no botão NEXT.
SQL4
Na tela SCRIPT WIZARD SUMMARY certique-se que a opção Script Data esteja como True e clique no botão FINISH para que o script seja gerado.
SQL5
Com o script gerado basta executar em uma nova base de dados para obter uma cópia da base AdventureWorksLT2008 já com os dados.

Tipos de LINQ

O LINQ é um modelo de programação que introduz a consulta e manipulação de dados dentro do conceito da linguagem .NET. Entretanto o suporte completo ao LINQ requer algumas extensões na linguagem utilizada.

O código abaixo exibe um exemplo de utilização do LINQ para fazer uma consulta na tabela Customers da base de dados Northwind.

        static void Main(string[] args)
        {
            // DataContext utiliza a connectionstring
            NorthwindDataContext db = new NorthwindDataContext();

            // Seleciona a tabela para executar a consulta
            Table<Customer> Customers = db.GetTable<Customer>();

            //Consulta clientes da cidade London
            var query =
                from cust in Customers
                where cust.City == "London"
                select cust;

            Console.WriteLine("Empresa".PadRight(25) + "Nome".PadRight(25) + "Cidade");
            foreach (var customer in query)
                Console.WriteLine("{0}{1}{2}", customer.CompanyName.PadRight(25),
                    customer.ContactName.PadRight(25), customer.City);
            Console.ReadKey();

        }
O resultado do código é:

Capture1
O código traz os dados de todos os Customers cuja cidade seja “London”. A consulta foi feita em uma Base de Dados SQL, mas a mesma consulta poderia ser utilizada se Customer fosse uma tabela em um DataSet ou uma classe que descreve uma tabela.

Um outro aspecto importante da integração da linguagem é a checagem de tipo. A sintaxe das expressões de consulta não tem o compromisso da checagem de tipo, pois os dados são sempre fortemente tipados, incluindo as consultas de coleções ou entidades que são lidas ou retornadas.
Atualmente existem os seguintes tipos de LINQ:
  • LINQ to Objects
  • LINQ to ADO.NET que inclui LINQ to SQL, LINQ to Dataset e LINQ to Entities
  • LINQ to XML
Cada uma destas implementações é definida através de um conjunto de extensões de métodos que implementam os operadores necessários para que o LINQ rode com uma fonte de dados particular. O acesso destas características é controlado por Namespaces.

LINQ to Objects
O LINQ to Object tem o objetivo de manipular coleções de objetos, que podem estar relacionados entre si de forma hierarquica ou gráfica. O LINQ to Object não está limitado a coleções de dados gerados pelo usuário. Como exemplo segue uma consulta de todos os arquivos que estão localizados no diretório temporário maiores de 500KB ordenados de forma decrescente:
        static void Main(string[] args)
        {
            string tempPath = Path.GetTempPath();
            DirectoryInfo dirInfo = new DirectoryInfo(tempPath);

            var query =
                from f in dirInfo.GetFiles()
                where f.Length > 500000 // Arquivos maior que 500K
                orderby f.Length descending
                select f;

            foreach (var arq in query)
                Console.WriteLine("Name = {0}", arq.Name);

            Console.ReadKey();
        }

O resultado do código acima é similar a figura abaixo. O resultado vai depender do conteúdo existente no diretório temporário da máquina que está sendo utilizada.
image
LINQ to ADO.NET
O LINQ to ADO.NET inclui diferentes implementações que compartilham da necessidade de manipular dados relacionais.
  • LINQ to SQL: é um componente do projeto LINQ que tem a capacidade de executar consultas em uma Base de Dados relacional.
  • LINQ to Entities: É muito similar ao LINQ TO SQL, entretanto ao invés de usar uma Base de Dados física como uma camada de persistência, ele usa uma entidade conceitual o EDM (Entity Data Model). O resultado da abstração é uma camada independente da camada de dados física.
  • LINQ to DataSet: Permite fazer consulta em um DataSet.
O código abaixo exemplifica uma consulta feita em um dataset tipado utilizando o LINQ to Dataset:
        static void Main(string[] args)
        {
            // Inicializa o TableAdapter da base de dados Northwind
            dsNorthwindTableAdapters.OrdersTableAdapter taNorthwind =
                new dsNorthwindTableAdapters.OrdersTableAdapter();

            // Declaração do DataSet Tipado da base de dados Northwind
            dsNorthwind ds = new dsNorthwind();
            taNorthwind.Fill(ds.Orders);

            // Consulta que traz o Id e a data do pedido para pedidos
            // com data superior a 01/01/1998
            var query = from o in ds.Orders
                        where o.OrderDate >= new DateTime(1998, 01, 01)
                        select new { o.OrderID, o.OrderDate };

            Console.WriteLine("Id do Pedido\t" + "Data do Pedido");

            foreach (var order in query)
            {
                Console.WriteLine("{0}\t\t{1}", order.OrderID,
                    order.OrderDate);
            }

            Console.ReadKey();           
        }

LINQ to XML
O LINQ to XML oferece uma sintaxe um pouco diferente para operar com dados em XML, permitindo consulta e manipulação de dados.

O código abaixo exemplifica uma busca no arquivo XML que possui informações sobre uma coleção de livros.

Arquivo Xml utilizado como fonte da busca:
<?xml version="1.0" encoding="utf-8" ?>
<livros>
  <livro linguagem="csharp">
    <titulo>Introducao ao CSharp</titulo>
    <autor>Antonio Dias</autor>
    <ano>2002</ano>
  </livro>
  <livro linguagem="vb">
    <titulo>Introducao ao Visual Basic</titulo>
    <autor>Marcelo Gomes</autor>
    <ano>2005</ano>
  </livro>
  <livro linguagem="csharp">
    <titulo>CSharp Avancado</titulo>
    <autor>Jose Maria</autor>
    <ano>2008</ano>
  </livro>
</livros>
Código utilizado para trazer os livros cuja linguagem seja vb:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Carrega o arquivo Xml
            XElement root = XElement.Load("livros.xml");
            // Faz uma busca nos livros cujo atributo linguagem seja vb
            IEnumerable<XElement> linguagem =
                from el in root.Elements("livro")
                where (string)el.Attribute("linguagem") == "vb"
                select el;
            // Exibe o resultado da pesquisa
            foreach (XElement el in linguagem)
                Console.WriteLine(el);

            Console.ReadKey();           
        }
    }
}
Resultado obtido:
image
Como conclusão podemos observar que o LINQ é uma linguagem que permite uma uniformidade para acessar dados em diversas fontes de informações.

Insert, Update e Delete com LINQ

Em um dos projetos que estive trabalhando, precisei interagir com uma Base de Dados que possuia tabelas sem chave primária, e por motivos de compatibilidade com projetos anteriores não podia alterar as tabelas para adequar as boas práticas de desenvolvimento e inserir as chaves primárias.
No meu projeto eu precisava inserir, deletar e fazer atualizações nos registros das tabelas. Resolvi utilizar o LINQ para fazer estas operações.

O LINQ não permite a inserção, exclusão e atualização em tabelas que não possuam chave primária, por isso tive que utilizar um recurso para poder trabalhar com estas tabelas.

Criei uma base de dados com uma tabela exemplo sem chave primária, para poder demonstrar uma forma de resolver este tipo de problema. A base de dados chama-se Escola e a tabela Aluno.
Criei a classe DBML no Visual Studio que tem o seguinte formato:

AlunoDbml
Para inserir o registro de um aluno na tabela podemos utilizar o seguinte código:
        /// <summary>
        /// Executa uma instrucao Insert inserindo um aluno na
        /// base de dados.
        /// </summary>
        static void Insert()
        {
            try
            {
                EscolaClassesDataContext context = 
                    new EscolaClassesDataContext();
 
                Aluno aluno = new Aluno();
                aluno.Matricula = "67489";
                aluno.Nome = "Douglas Xavier";
                aluno.Idade = 35;
                aluno.Curso = "SQL Server";
                aluno.Endereco = "Rua Germano Souza, 675";
 
                context.Alunos.InsertOnSubmit(aluno);
                context.SubmitChanges();
 
                Console.WriteLine("Aluno inserido com sucesso!");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
Executando o método Insert() o aluno não é inserido na tabela e obtemos a seguinte mensagem de erro:
Can’t perform Create, Update or Delete Operations on Table(Aluno) because it has no primary key.
A mensagem indica que o LINQ não permite a inserção, exclusão ou atualização de registros em tabelas que não possuam chave primária.

Se testarmos o método Delete(), cujo código é exibido abaixo, obtemos a mesma mensagem de erro do método Insert():

        /// <summary>
        /// Executa uma instrucao Delete, apagando os dados de um aluno.
        /// </summary>
        static void Delete()
        {
            try
            {
                EscolaClassesDataContext context = new 
                    EscolaClassesDataContext();
 
                Aluno aluno = (from a in context.Alunos
                               where a.Idade == 22
                               select a).First();
 
                context.Alunos.DeleteOnSubmit(aluno);
                context.SubmitChanges();
                Console.WriteLine("O aluno foi apagado com sucesso!");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
Se testarmos o método Update(), cujo código é exibido abaixo, a atualização não será feita pois o método de Update do LINQ não gera exceção, por isso é muito dificil detectar qual problema está acontecendo, já que a atualização não é feita e nenhuma exceção é indicada.

        /// <summary>
        /// Executa uma instrucao Update atualizando um endereco de 
        /// um determinado aluno
        /// </summary>
        static void Update()
        {
            try
            {
                EscolaClassesDataContext context = new 
                    EscolaClassesDataContext();
 
                Aluno aluno = (from a in context.Alunos
                               where a.Nome == "Renato Borges"
                               select a).First();
                aluno.Endereco = "Av Otaviano Silva, 47";
 
                context.SubmitChanges();
                Console.WriteLine("A atualização foi realizada com" +
                    " sucesso!");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
Como resolver este problema se não podemos mexer na estrutura da tabela do banco de dados? A solução é criar uma chave primária na classe DBML. Esta chave pode ser definida em qualquer coluna. Esta definição não afeta a estrutura da tabela na base de dados, é somente um recurso para podermos trabalhar com LINQ em tabelas sem chave primária.

Para inserir uma chave primária em uma coluna da tabela Aluno, devemos alterar a propriedade PrimaryKey de False para True da coluna que servirá como chave primária.

Selecionei a coluna Matricula para servir de chave primária. Veja a figura abaixo de como ficou a definição da classe DBML.

ChavePrimaria 
Após a configuração da chave primária os métodos Insert(), Update() e Delete() podem ser executados.

Inseri o código para fazer um Select na tabela e visualizar todos os métodos sendo executados sem erros.
/// <summary>
/// Executa uma instrucao Select na base de dados.
/// </summary>
static void Select()
{
    // Cria a conexão com a base de dados Escola
    EscolaClassesDataContext context = new EscolaClassesDataContext();
 
    // Faz um SELECT na base de dados para imprimr a relação de alunos
    var queryAlunos = from a in context.Alunos
                      select a;
 
    if (queryAlunos.Count() > 0)
    {
        foreach (var item in queryAlunos)
        {
            Console.WriteLine("=========================================");
            Console.WriteLine("Matrícula: " + item.Matricula);
            Console.WriteLine("Nome: " + item.Nome);
            Console.WriteLine("Idade: " + item.Idade);
            Console.WriteLine("Curso: " + item.Curso);
            Console.WriteLine("Endereço: " + item.Endereco);
            Console.WriteLine("=======================================\r\n");
 
        }
    }
}
O código que utilizei no projeto para chamar todos os métodos foi:
        static void Main(string[] args)
        {
            Console.WriteLine("Executa o método Insert");
            Insert();
 
            Console.WriteLine("Executa o método Update");
            Update();
 
            Console.WriteLine("Executa o método Delete");
            Delete();
 
            Console.WriteLine("Executa o método Select");
            Select();
 
            Console.ReadKey();
        }
Segue a tela com o resultado dos métodos executados com sucesso:
Resultado
Os conceitos utilizados nos métodos Insert(), Update() e Delete() mostrados neste post podem ser utilizados normalmente, apenas acrescentei uma possível solução para o problema de precisar trabalhar com tabelas sem chaves primárias.

Escrita e Leitura de Arquivo no Android – Parte I

Os dispositivos com o Android permitem a escrita e leitura de arquivos na memória interna. Este post descreve como criar um arquivo texto, escrever e visualizar o conteúdo do arquivo no próprio dispositivo e como exportar o arquivo do emulador do Eclipse para o computador.

Como cada aplicação do Android roda em seu próprio contexto, o arquivo só pode ser manipulado pela própria aplicação que criou o arquivo. O arquivo não é visualizado por outras aplicações e não pode ser manipulado pelo usuário.

O projeto utilizado para demonstrar a escrita e leitura do arquivo possui uma Activity com o seguinte layout:
arquivo1
Foi criada uma classe chamada ManageFile com os métodos WriteFile e ReadFile, que fazem a escrita e leitura do arquivo.


/**
* Classe responsável pela escrita e leitura de arquivo.
* @author Romar Consultoria
*
*/
public class ManageFile {
private static final String TAG = "ManageFile";
private Context context;

public ManageFile(Context context){
this.context = context;
}

/**
* Escreve no arquivo texto.
* @param text Texto a ser escrito.
* @return True se o texto foi escrito com sucesso.
*/
public boolean WriteFile(String text){
try {
// Abre o arquivo para escrita ou cria se não existir
FileOutputStream out = context.openFileOutput("romar.txt",
Context.MODE_APPEND);
out.write(text.getBytes());
out.write("\n".getBytes());
out.flush();
out.close(); 
return true;

} catch (Exception e) {
Log.e(TAG, e.toString());
return false;
}
}

/**
* Faz a leitura do arquivo
* @return O texto lido.
* @throws FileNotFoundException
* @throws IOException
*/
public String ReadFile() throws FileNotFoundException, IOException{
File file = context.getFilesDir();
File textfile = new File(file + "/romar.txt");

FileInputStream input = context.openFileInput("romar.txt");
byte[] buffer = new byte[(int)textfile.length()];

input.read(buffer);   

return new String(buffer);
}
}



Observe que no construtor da classe foi passado o argumento context da aplicação para que seja possível manipular o arquivo no contexto da aplicação.

O método writeFile tem como argumento o texto a ser escrito e utiliza o método openFileOutput para criar se o arquivo não existir ou abrir o arquivo anteriormente criado.

O método readFile busca pelo arquivo (romar.txt) criado anteriormente, faz a leitura do arquivo e retorna uma String com o conteúdo.

Na Activity do projeto foram criados dois botões Ler e Escrever que fazem a chamada dos métodos de escrita e leitura do arquivo:



@Override
public void onClick(View v) {

try {
switch (v.getId()) {
case R.id.btnRead:
// Faz a leitura do arquivo
ManageFile fileread = new ManageFile(this);
textRead.setText(fileread.ReadFile());
break;

case R.id.btnWrite:
ManageFile filewrite = new ManageFile(this);

// Avisa o usuário se a gravação foi bem sucedida
if(filewrite.WriteFile(editText.getText().toString()) == true){
Toast.makeText(this, "Texto gravado com sucesso.",
Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(this, "Não foi possível escrever o texto.", 
Toast.LENGTH_SHORT).show();
}

// Reseta o campo do texto
editText.setText("");

break;

default:
break;
}

} catch (FileNotFoundException e) {
Log.e(TAG, e.toString());
} catch (IOException e) {
Log.e(TAG, e.toString());
}

}



A aplicação funcionando apresenta a seguinte tela:

arquivo2

É possível exportar o arquivo (romar.txt) para o computador, através do emulador do Eclipse utilizando a perspectiva DDMS, na aba File Explorer que exibe o conteúdo nos diretórios do dispositivo.

arquivo3

No diretório data/data/nome_do_pacote/files fica localizado os arquivos, criados dentro do contexto da aplicação. Para exportar o arquivo selecionar o botão indicado pela seta e gravar o arquivo no computador. Mas lembre-se o acesso a este arquivo só é possível via emulador, no dispositivo real não é possível acessar este arquivo.

Escrita e Leitura de Arquivo no Android–Parte II

No primeiro post sobre escrita e leitura de arquivos no Android descrevi como ler e escrever na memória interna do Android. Este post descreve como escrever e ler em arquivos armazenados no cartão SD do dispositivo.

Antes de optar em gravar informações no cartão SD do dispositivo, lembre-se que o usuário terá acesso as informações deste arquivo e poderá apagá-lo a qualquer momento. Também não existe a garantia do arquivo não ser apagado durante a conexão ou desconexão do dispositivo com o microcomputador, caso o mesmo seja desconectado de forma incorreta.

Para demonstrar a leitura e escrita no cartão SD, vou utilizar o mesmo layout do projeto da parte I do post:
arquivo1
Antes de criar e escrever em um arquivo no cartão SD de um dispositivo, é necessário verificar se o cartão está inserido, quais são as permissões de escrita e leitura, se o sistema de arquivo é reconhecido pelo dispositivo, entre outras informações.

Foi criada a classe ManageFile com o método getStateSDcard() que verifica o estado do cartão SD, o método  WriteFile() que faz a escrita do texto e o método ReadFile() que faz a leitura do texto armazenado no cartão SD.

public class ManageFile {
    private static final String TAG = "ManageFile";
    private Context context;
    private boolean sdCardAvailable;
    private boolean sdCardWritableReadable;
    private boolean sdCardReadableOnly;
    
    public ManageFile(Context context){
        this.context = context;
    }

    /**
     * Escreve no arquivo texto.
     * @param text Texto a ser escrito.
     * @return True se o texto foi escrito com sucesso.
     */
    public boolean WriteFile(String text){
        try {
            File file = new File(context.getExternalFilesDir(null),
                "romar.txt");
            FileOutputStream out = new FileOutputStream(file, true);
            out.write(text.getBytes());
            out.write("\n".getBytes());
            out.flush();
            out.close();    
            return true;
            
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            return false;
        }
    }
    
    /**
     * Faz a leitura do arquivo
     * @return O texto lido.
     * @throws FileNotFoundException
     * @throws IOException
     */
    public String ReadFile() throws FileNotFoundException, IOException{
        File textfile = new File(context.getExternalFilesDir(null),
            "romar.txt");

        FileInputStream input = new FileInputStream(textfile);
        byte[] buffer = new byte[(int)textfile.length()];
        
        input.read(buffer);            
        
        return new String(buffer);
    }
    
    public void getStateSDcard(){
        
        // Obtêm o status do cartão SD
        String status = Environment.getExternalStorageState();
        
        if (Environment.MEDIA_BAD_REMOVAL.equals(status)) {
            // Midia foi removida antes de ser montada
            sdCardAvailable = false;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia removida.");
        }
        else if (Environment.MEDIA_CHECKING.equals(status)) {
            // Midia está presente e está sendo feita a verificação
            sdCardAvailable = true;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia sendo verificada.");
        }
        else if (Environment.MEDIA_MOUNTED.equals(status)) {
            // A midia está presente e montada neste momento com
            // permissão de escrita e leitura
            sdCardAvailable = true;
            sdCardWritableReadable = true;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia com permissão de escrita e leitura.");
        }
        else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(status)) {
            // A midia está presente e montada neste momento com 
            // permissão somente de leitura
            sdCardAvailable = true;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia com permissão somente leitura.");
        }
        else if (Environment.MEDIA_NOFS.equals(status)) {
            // A midia está presente, mas está vazia ou utilizando um
            // sistema de arquivos não suportado    
            sdCardAvailable = false;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia com sistema de arquivos não compatível.");
        }
        else if (Environment.MEDIA_REMOVED.equals(status)) {
            // A midia não está presente
            sdCardAvailable = false;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia não presente.");
        }
        else if (Environment.MEDIA_SHARED.equals(status)) {
            // A midia está presente, não montada e compartilhada 
            // via USB
            sdCardAvailable = false;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia compartilhada via USB.");
        }
        else if (Environment.MEDIA_UNMOUNTABLE.equals(status)) {
            // A midia está presente mas não pode ser montada
            sdCardAvailable = false;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia não pode ser montada");
        }
        else if (Environment.MEDIA_UNMOUNTED.equals(status)) {
            // A midia está presente mas não montada
            sdCardAvailable = false;
            sdCardWritableReadable = false;
            sdCardReadableOnly = false;
            Log.d(TAG, "Midia não montada.");
        }
    }

    public boolean isSdCardAvailable() {
        return sdCardAvailable;
    }

    public void setSdCardAvailable(boolean sdCardAvailable) {
        this.sdCardAvailable = sdCardAvailable;
    }

    public boolean isSdCardWritableReadable() {
        return sdCardWritableReadable;
    }

    public void setSdCardWritableReadable(boolean sdCardWritableReadable) {
        this.sdCardWritableReadable = sdCardWritableReadable;
    }

    public boolean isSdCardReadableOnly() {
        return sdCardReadableOnly;
    }

    public void setSdCardReadableOnly(boolean sdCardReadableOnly) {
        this.sdCardReadableOnly = sdCardReadableOnly;
    }

}

No método getStateSDcard() foi utilizado o método getExternalStorageState() da classe Environment do Android que retorna o estado do cartão SD. Para cada estado retornado é setado um flag para indicar se o cartão está disponível, se permite escrita e leitura, ou se permite somente somente leitura.

No método WriteFile() foi utilizado o método getExternalFilesDir() do Android para obter o diretório do cartão SD. Foi passado o valor null como parâmetro, para que seja retornada a raiz do diretório da aplicação na área de armazenamento externa, no nosso caso o cartão SD (válido para a API 8, vide nota abaixo).

O arquivo vai ser escrito no diretório:
/Android/data/<nome_pacote>/files/
onde nome_pacote é o pacote da aplicação, no nosso caso br.com.romar.

NOTA: O método getExternalFilesDir() deve ser utilizado em projetos que rodam a partir da API 8. Para projetos que rodam na API 7 ou inferior, deverá ser utilizado o método getExternalStorageDirectory(), que irá retornar a raiz do diretório do cartão SD. Neste caso o arquivo será gravado na raiz do cartão e não no diretório da aplicação. No caso da API 8, quando a aplicação for desinstalada os arquivos que estiverem no cartão SD no diretório /Android/data/<nome_pacote>/files serão apagados.

No método ReadFile() foi utilizado o mesmo método  getExternalFilesDir() do Android para abrir o arquivo gravado anteriormente para leitura.

Na Activity principal do projeto foi criado um botão para escrita e leitura do arquivo. Segue o código que trata o click em cada um dos botões:
@Override
public void onClick(View v) {
    
try {
    switch (v.getId()) {
    case R.id.btnRead: // Faz a leitura do arquivo    
        
        // Verifica se o sdcard tem permissão para leitura
        if (managefile.isSdCardAvailable() && 
                (managefile.isSdCardReadableOnly() || 
                        managefile.isSdCardWritableReadable())) {
            managefile.getStateSDcard();
            textRead.setText(managefile.ReadFile());                    
        }
        else {
            Toast.makeText(this, 
                "O cartão SD não está disponível, ou não permite" +
                " leitura", Toast.LENGTH_SHORT).show();
        }            
        break;

    case R.id.btnWrite: // Faz a escrita do arquivo
        
        // Verifica se o sdcard tem permissão para escrita
        if (managefile.isSdCardAvailable() &&
                managefile.isSdCardWritableReadable()) {
            // Avisa o usuário se a gravação foi bem sucedida
            if(managefile.WriteFile(editText.getText().toString()) == 
                true){
                Toast.makeText(this, 
                    "Texto gravado com sucesso.",
                    Toast.LENGTH_SHORT).show();
            }
            else{
                Toast.makeText(this, 
                    "Não foi possível escrever o texto.", 
                    Toast.LENGTH_SHORT).show();
            }
        }
        else {
            Toast.makeText(this, 
                    "O cartão SD não está disponível, 
                    ou não permite" +
                    " escrita.", Toast.LENGTH_SHORT).show();                    
        }        
        
        // Reseta o campo do texto
        editText.setText("");
        
        break;
        
    default:
        break;
    }

} catch (FileNotFoundException e) {
    Log.e(TAG, e.toString());
} catch (IOException e) {
    Log.e(TAG, e.toString());
}

}

Antes de fazer a escrita ou leitura no arquivo, o código verifica primeiro se o cartão SD está disponível e permite escrita ou leitura dependendo do caso.

O cartão SD do dispositivo por padrão possui somente permissão de leitura. Para que seja possível escrever no cartão é necessário que no momento da instalação da aplicação o usuário dê a permissão de escrita na área de armazenamento externo. Para que a aplicação tenha a permissão de escrita é necessário inserir no arquivo AndroidManifest.xml da aplicação a seguinte tag:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Exibindo uma Activity no Android

No sistema operacional do Android as telas que são apresentadas para o usuário são chamadas de Activity. Este post demonstra como chamar uma Activity “filha” através de uma Activity “pai” e retornar uma resposta quando o usuário retorna para a Activity “pai”.

Para demonstrar a chamada da Activity foi utilizado o mesmo projeto do post Criando um ListAdapter Customizado para o ListView do Android como projeto de partida. No projeto foi criado um ListView que exibe as informações dos Estados do Brasil.

Neste post será implementada a chamada de uma Activity “filha” para exibir os detalhes do Estado que foi selecionado pelo usuário. Na Figura 1 temos a Activity principal que exibe a relação de Estados do Brasil, juntamente com as informações da Capital e Área do Estado. Na Figura 2 temos a Activity “filha” que exibe os detalhes do Estado selecionado pelo usuário. No exemplo o Estado selecionado foi Rio Grande do Norte.

figura_1_2
Para exibir a Activity “filha” foi necessário fazer algumas inserções no código do projeto de partida, demonstrado na Listagem 1:

Listagem 1:
   1:  package br.com.romar;
   2:   
   3:  import java.util.ArrayList;
   4:  import java.util.List;
   5:   
   6:  import android.app.ListActivity;
   7:  import android.content.Intent;
   8:  import android.os.Bundle;
   9:  import android.view.View;
  10:  import android.widget.AdapterView;
  11:  import android.widget.ListView;
  12:  import android.widget.AdapterView.OnItemClickListener;
  13:   
  14:  public class Main extends ListActivity implements OnItemClickListener {
  15:      List<State> stateList;
  16:      
  17:      /** Called when the activity is first created. */
  18:      @Override
  19:      public void onCreate(Bundle savedInstanceState) {
  20:          super.onCreate(savedInstanceState);
  21:          
  22:          stateList = new ArrayList<State>();
  23:          
  24:          for (int i = 0; i < states.length; i++) {
  25:              State state = new State();
  26:              state.setState(states[i][0]);
  27:              state.setAbbreviation(states[i][1]);
  28:              state.setCapital(states[i][2]);
  29:              state.setArea(Float.parseFloat(states[i][3]));
  30:              state.setBanner(images[i]);
  31:              
  32:              stateList.add(state);
  33:          }  
  34:          
  35:          setListAdapter(new StateAdapter(this, stateList));
  36:          
  37:          ListView listview = getListView();
  38:          
  39:          listview.setOnItemClickListener(this);
  40:      }
  41:      
  42:      @Override
  43:      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  44:          
  45:          // Obtêm os detalhes do Estado selecionado
  46:          State state = stateList.get(position);
  47:          
  48:          // Exibe a Activity com os detalhes dos Estados
  49:          Intent intent = new Intent(this, DetailActivity.class);
  50:          Bundle params = new Bundle();
  51:          params.putString("estado", state.getState());
  52:          params.putString("capital", state.getCapital());
  53:          params.putFloat("area", state.getArea());
  54:          intent.putExtras(params);
  55:          
  56:          startActivity(intent);
  57:      }
  58:   
  59:      
  60:      private String[][] states = new String[][]{
  61:             {"Acre", "AC", "Rio Branco", "152581.4"},
  62:             {"Alagoas", "AL", "Maceió", "27767.7"},
  63:             {"Amapá", "AP", "Macapá", "142814.6"},
  64:             {"Amazonas", "AM", "Manaus", "1570745.7"},
  65:             {"Bahia", "BA", "Salvador", "564692.7"},
  66:             {"Ceará", "CE", "Fortaleza", "148825.6"},
  67:             {"Distrito Federal", "DF", "Brasília", "5822.1"},
  68:             {"Espírito Santo", "ES", "Vitória", "46077.5"},
  69:             {"Goiás", "GO", "Goiânia", "340086.7"},
  70:             {"Maranhão", "MA", "São Luís", "331983.3"},
  71:             {"Mato Grosso", "MT", "Cuiabá", "903357.9"},
  72:             {"Mato Grosso do Sul", "MS", "Campo Grande", "357125.0"},
  73:             {"Minas Gerais", "MG", "Belo Horizonte", "586528.3"},
  74:             {"Pará", "PA", "Belém", "1247689.5"},
  75:             {"Paraíba", "PB", "João Pessoa", "56439.8"},
  76:             {"Paraná", "PR", "Curitiba", "199314.9"},
  77:             {"Pernambuco", "PE", "Recife", "98311.6"},
  78:             {"Piauí", "PI", "Teresina", "251529.2"},
  79:             {"Rio de Janeiro", "RJ", "Rio de Janeiro", "43696.1"},
  80:             {"Rio Grande do Norte", "RN", "Natal", "52796.8"},
  81:             {"Rio Grande do Sul", "RS", "Porto Alegre", "281748.5"},
  82:             {"Rondônia", "RO", "Porto Velho", "237576.2"},
  83:             {"Roraima", "RR", "Boa Vista", "224299.0"},
  84:             {"Santa Catarina", "SC", "Florianópolis", "95346.2"},
  85:             {"São Paulo", "SP", "São Paulo", "248209.4"},
  86:             {"Sergipe", "SE", "Aracaju", "21910.3"},
  87:             {"Tocantins", "TO", "Palmas", "277620.9"}
  88:         };
  89:      
  90:      private int[] images = new int[]{
  91:              R.drawable.acre,
  92:              R.drawable.alagoas,
  93:              R.drawable.amapa,
  94:              R.drawable.amazonas,
  95:              R.drawable.bahia,
  96:              R.drawable.ceara,
  97:              R.drawable.distritofederal,
  98:              R.drawable.espiritosanto,
  99:              R.drawable.goias,
 100:              R.drawable.maranhao,
 101:              R.drawable.matogrosso,
 102:              R.drawable.matogrossosul,
 103:              R.drawable.minasgerais,
 104:              R.drawable.para,
 105:              R.drawable.paraiba,
 106:              R.drawable.parana,
 107:              R.drawable.pernambuco,
 108:              R.drawable.piaui,
 109:              R.drawable.riojaneiro,
 110:              R.drawable.riograndenorte,
 111:              R.drawable.riograndesul,
 112:              R.drawable.rondonia,
 113:              R.drawable.roraima,
 114:              R.drawable.santacatarina,
 115:              R.drawable.saopaulo,
 116:              R.drawable.sergipe,
 117:              R.drawable.tocatins
 118:      };
 119:   
 120:  }

Na linha 14 foi implementada a interface OnItemClickListener.

Na linha 37 foi obtido o widget ListView da Activity através do método getListView().

Na linha 39 foi registrado um callback para ser chamado quando um item no AdapterView é clicado, através do método setOnItemClickListener(AdapterView.OnItemClick listener).

Nas linhas 42 a 57 é sobrescrito o método onItemClick(AdapterView<?> parent, View view, int position, long id), que obtêm o Estado selecionado e as informações a serem exibidas na segunda Activity. Através do método startActivity(Intent) a Activity da Figura 2 é exibida.

Nas linhas 49 a 54 é criado o objeto intent e o objeto params onde são definidas as informações que serão passadas para a Activity “filha”.

Para que seja exibida a Activity “filha” (chamada de DetailActivity) não esquecer de declarar a Activity  no arquivo AndroidManifest.xml do projeto.
        <activity android:name=".DetailActivity" android:label="Detalhes do Estado"/>
No arquivo da Activity DetailActivity no método OnCreate(Bundle savedInstanceState) são obtidos os valores das informações passadas pela Activity “pai”, demonstrado na Listagem 2.

Listagem 2:
   1:      @Override
   2:      protected void onCreate(Bundle savedInstanceState) {
   3:          super.onCreate(savedInstanceState);
   4:          
   5:          // Configura o layout da Activity
   6:          setContentView(R.layout.state_detail);
   7:          
   8:          // Widgets da Activity
   9:          TextView textEstado = (TextView)findViewById(R.id.textEstado);
  10:          TextView textCapital = (TextView)findViewById(R.id.textCapital);
  11:          TextView textArea = (TextView)findViewById(R.id.textArea);
  12:          
  13:          // Carrega os valores passados pela Activity Principal (Main)
  14:          Intent intent = getIntent();
  15:          
  16:          if (intent != null) {
  17:              textEstado.setText(intent.getStringExtra("estado"));
  18:              textCapital.setText(intent.getStringExtra("capital"));
  19:              textArea.setText(String.valueOf(intent.getFloatExtra("area", 0)));            
  20:          }
  21:      }

Na linha 14 as informações passadas pela Activity “pai” são obtidas através do método getIntent().
Agora, e se com a apresentação da Activity “filha” quisessemos preencher algum campo e retornar esta informação para a Activity “pai”? Isto pode ser feito utilizando-se o método startActivityForResult(Intent, int) ao invés do método startActivity(Intent) utilizado anteriormente.

O método startActivityForResult(Intent, int)  faz a chamada de outra Activity, só que desta vez além do parâmetro Intent é passado como parâmetro um número inteiro utilizado para identificar a chamada.O resultado retornará através do método onActivityResult(int, int, Intent).

Quando saímos de uma Activity, ela pode chamar o método setResult(int) para retornar um dado de volta para a Activity chamadora. A Activity deve fornecer um código, que pode ser os resultados padrão RESULT_CANCELED, RESULT_OK, ou qualquer valor customizado. Opcionalmente ela pode retornar um Intent contendo dados adicionais.

Para demonstrar como retornar dados de uma Activity “filha”, vou utilizar o mesmo projeto fazendo pequenas alterações.

Na classe Main exibida na Listagem 1 acima, incluir a declaração da seguinte constante na classe.
private final static int CALL_SECOND_ACTIVITY = 1;

Esta constante define o valor de identificação a ser passado no segundo parâmetro do método startActivityForResult(Intent, int).

No método onItemClick(AdapterView<?> parent, View view, int position, long id) demonstrado na linha 43 da Listagem 1, substituir a chamada da Activity startActivity(Intent) (linha 56) pela linha abaixo:

startActivityForResult(intent,CALL_SECOND_ACTIVITY);




No código da Listagem 1 incluir o método onActivityResult(int, int, Intent), conforme demonstrado abaixo na Listagem 3:

Listagem 3:
   1:      /**
   2:       * Método chamado quando saímos da DetailActivity
   3:       * @param requestCode O código inteiro originalmente fornecido
   4:       * no método startActivityForResult(), permitindo identificar
   5:       * quem fez a chamada.
   6:       * @param resultCode O código inteiro retornado pela Activity
   7:       * filha através do método setResult().
   8:       * @param data Um Intent que pode retornar dados ao chamador.
   9:       */
  10:      @Override
  11:      protected void onActivityResult(int requestCode, 
  12:              int resultCode, Intent data) {
  13:          super.onActivityResult(requestCode, resultCode, data);
  14:          
  15:          // Verifica se é o código de quem fez a chamada
  16:          if (requestCode == CALL_SECOND_ACTIVITY) {
  17:              // Verifica se o retorno foi com sucesso
  18:              if (resultCode ==RESULT_OK) {
  19:                  if (data != null) {
  20:                      Bundle params = data.getExtras();
  21:                      if (params.getBoolean("visitar") == true) {
  22:                           Toast.makeText(this, "Quero visitar " + 
  23:                                   params.getString("estado"), 
  24:                                   Toast.LENGTH_SHORT).show();
  25:                      }
  26:                  }
  27:              }
  28:          }
  29:      }

Este método será chamado quando a Activity “filha” for fechada.
Na linha 16 é verificado se o código da identificação representa o mesmo código passado na chamada.

Na linha 21 através da classe Bundle obtemos a informação se o usuário deseja visitar o Estado e o nome do Estado.

Substituir o código da Listagem 2 pelo da Listagem 4:
Listagem 4:
   1:  public class DetailActivity extends Activity implements OnClickListener {
   2:      TextView textEstado;
   3:      TextView textCapital;
   4:      TextView textArea;
   5:      
   6:      @Override
   7:      protected void onCreate(Bundle savedInstanceState) {
   8:          super.onCreate(savedInstanceState);
   9:          
  10:          // Configura o layout da Activity
  11:          setContentView(R.layout.state_detail);
  12:          
  13:          // Widgets da Activity
  14:          textEstado = (TextView)findViewById(R.id.textEstado);
  15:          textCapital = (TextView)findViewById(R.id.textCapital);
  16:          textArea = (TextView)findViewById(R.id.textArea);
  17:          
  18:          // Carrega os valores passados pela Activity Principal (Main)
  19:          Intent intent = getIntent();
  20:          
  21:          if (intent != null) {
  22:              textEstado.setText(intent.getStringExtra("estado"));
  23:              textCapital.setText(intent.getStringExtra("capital"));
  24:              textArea.setText(String.valueOf(intent.getFloatExtra("area", 0)));            
  25:          }
  26:          
  27:          final RadioButton rbSim = (RadioButton)findViewById(R.id.rbSim);
  28:          final RadioButton rbNao = (RadioButton)findViewById(R.id.rbNao);
  29:          
  30:          rbSim.setOnClickListener(this);
  31:          rbNao.setOnClickListener(this);
  32:      }
  33:   
  34:      @Override
  35:      public void onClick(View v) {
  36:          Intent intent = new Intent();        
  37:          // Verifica qual radiobutton foi selecionado
  38:          if (v.getId() == R.id.rbSim) {            
  39:              intent.putExtra("visitar", true);
  40:              intent.putExtra("estado", textEstado.getText().toString());
  41:              setResult(RESULT_OK, intent);
  42:          }
  43:      }
  44:  }

Nas linhas 27 e 28 foram incluídas as declarações do botões RadioButton.
Nas linhas 30 3 31 foram registrados os eventos OnClickListener(View.OnClickListener) para os botões Sim e Não.

A partir da linha 35 temos o método que trata o botão selecionado.  Através de um Intent, é passado um boolean informando se deseja visitar o Estado e uma String com o nome do Estado.

Na linha 41 é utilizado o método setResult(int, Intent), para poder passar o resultado OK da operação e a Intent com as informações se deseja visitar, e o nome do Estado.

Executando a aplicação com as alterações descritas e selecionando o Estado Rio Grande do Norte com a opção de visitar, quando fechamos a Activity “filha” será apresentada uma mensagem com o nome do Estado que se deseja visitar, como apresentado na Figura 3:

figura3
Atualizado: Como comentado por um leitor do post ficou faltando o xml da Activity dos detalhes do estado, que segue abaixo:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:stretchColumns="2"
  android:padding="5dp"
  >
  
  <TableRow>
      <TextView
          android:layout_column="1" 
          android:text="Estado:" 
          android:padding="3dp"
          android:textSize="24sp"
       />
      <TextView
          android:layout_column="2" 
          android:hint="Nome do Estado..." 
          android:id="@+id/textEstado"
          android:textSize="24sp"
       />       
  </TableRow>
  
  <TableRow>
      <TextView
          android:layout_column="1" 
          android:text="Capital:" 
           android:textSize="24sp"
       />
      <TextView
          android:layout_column="2" 
          android:hint="Nome da Capital..." 
          android:id="@+id/textCapital"
          android:textSize="24sp"
       />       
  </TableRow>
  
  <TableRow>
      <TextView
          android:layout_column="1" 
          android:text="Área:"
           android:textSize="24sp"
       />
      <TextView
          android:layout_column="2" 
          android:hint="Area em Km2..." 
          android:id="@+id/textArea"
           android:textSize="24sp"
       />       
  </TableRow>
  
  <TableRow>
      <TextView
          android:layout_column="1" 
          android:text="Visitar?"
           android:textSize="24sp"
       />
    <RadioGroup
        android:orientation="horizontal"
    >
        <RadioButton
            android:id="@+id/rbSim" 
            android:text="Sim"
            android:layout_height="35dp"
              android:textSize="24sp"
        />
        <RadioButton 
            android:id="@+id/rbNao" 
            android:text="Não"
            android:layout_height="35dp"
              android:textSize="24sp"
        />        
    </RadioGroup>
  </TableRow>
      
</TableLayout>