Como Exibir Dados do SQLite no ExpandableList

Na documentação do Android existem alguns exemplos de utilização do ExpandableList que é similar ao ListView, com a diferença que o ExpandableList exibe uma lista que pode ser expandida em dois níveis. Os dados que são exibidos no ExpandableList podem ser fornecidos de várias fontes, como por exemplo arrays.

Este post vai utilizar como fonte de dados, para preencher o ExpandableList, uma base de dados SQLite, e para isso utiliza como base o mesmo projeto do post SQLite em Aplicações Android, que possui uma base de dados de livros armazenados em uma base SQLite. Esta versão exibe uma tela com o título do livro em um nível e o nome do autor, ano e genêro do livro em outro nível do ExpandableList.

device-2012-12-19-173925

Para exibir as informações do livro no ExpandableList é necessário inserir no projeto uma classe para exibir o ExpandableList (classe ExpandableActivity), um arquivo de layout para as informações que são exibidas no segundo nível (expandable_child.xml),  e uma classe Adapter (LivroAdapter) para expor os dados do livro no ExpandableList.

A classe ExpandableList herda da classe ExpandableListActivity e configura o Adapter que é utilizado para exibir as informações dos livros.

public class ExpandableActivity extends ExpandableListActivity {
    private LivroDb livroDb;
    
   @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        livroDb = new LivroDb(this);
        
        LivroAdapter livroAdapter = new LivroAdapter(livroDb.getCursor(),
                this);
        setListAdapter(livroAdapter);
   }
    
   @Override
   protected void onDestroy() {
        super.onDestroy();     
        // Fecha a base de dados
        livroDb.close();
   }
}


O layout para exibir os dados do segundo nível do ExpandableList pode ser customizado, neste caso foram utilizados somente TextView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/textAutor"
        android:layout_width="match_parent"
        android:layout_height="40dp"
     />

     <TextView 
        android:id="@+id/textAno"
        android:layout_width="match_parent"
        android:layout_height="40dp"
     />
        
    <TextView 
        android:id="@+id/textGenero"
        android:layout_width="match_parent"
        android:layout_height="40dp"
     />
</LinearLayout>

A classe LivroAdapter herda da classe CursorTreeAdapter que expõe dados utilizando um Cursor. Esta classe utiliza o Cursor fornecido pelo SQLite e os métodos que devem ser implementados são:

public class LivroAdapter extends CursorTreeAdapter {
      LayoutInflater layoutInflater;
      
      public LivroAdapter(Cursor cursor, Context context) {
            super(cursor, context);
            layoutInflater = LayoutInflater.from(context);
      }
 
      // Exibe as informações do segundo nível.
      @Override
      protected void bindChildView(View view, Context context, Cursor cursor,
                  boolean isLastChild) {
              TextView textAutor = (TextView)view.findViewById(R.id.textAutor);
              textAutor.setText(cursor.getString(cursor.getColumnIndex("autor")));
              TextView textAno = (TextView)view.findViewById(R.id.textAno);
              textAno.setText(cursor.getString(cursor.getColumnIndex("ano_publicacao")));
              TextView textGenero = (TextView)view.findViewById(R.id.textGenero);
              textGenero.setText(cursor.getString(cursor.getColumnIndex("genero")));              
      }
 
      // Exibe as informações do primeiro nível.
      @Override
      protected void bindGroupView(View view, Context context, Cursor cursor,
                  boolean isExpanded) {
            TextView textTitulo = (TextView)view.findViewById(android.R.id.text1);
            textTitulo.setText(cursor.getString(cursor.getColumnIndex("titulo")));
      }
 
      // Lê o Cursor da base SQLite.
      @Override
      protected Cursor getChildrenCursor(Cursor groupCursor) {
            int id = groupCursor.getInt(groupCursor.getColumnIndex("_id"));
            return Principal.livros.getCursorById(id);
      }
 
      // Monta o layout do segundo nível.
      @Override
      protected View newChildView(Context context, Cursor cursor,
                  boolean isLastChild, ViewGroup parent) {
              View view = layoutInflater.inflate(
                R.layout.expandable_child, null);
              TextView textAutor = (TextView)view.findViewById(R.id.textAutor);
              textAutor.setText(cursor.getString(cursor.getColumnIndex("autor")));
              TextView textAno = (TextView)view.findViewById(R.id.textAno);
              textAno.setText(cursor.getString(cursor.getColumnIndex("ano_publicacao")));
              TextView textGenero = (TextView)view.findViewById(R.id.textGenero);
              textGenero.setText(cursor.getString(cursor.getColumnIndex("genero")));              
              
            return view;
      }
 
      // Monta o layout do primeiro nível.
      @Override
      protected View newGroupView(Context context, Cursor cursor,
                  boolean isExpanded, ViewGroup parent) {
            View view = layoutInflater.inflate(
                        android.R.layout.simple_expandable_list_item_1, null);
            TextView textTitulo = (TextView)view.findViewById(android.R.id.text1);
            textTitulo.setText(cursor.getString(cursor.getColumnIndex("titulo")));
            return view;
      }
 
}

As informações dos livros são exibidas no ExpandableList da seguinte forma:
imagem

O código fonte do projeto pode ser obtido no link Download do Projeto.

O emulador Android utilizado foi configurado com a API 15 (Android 4.0.3 Ice Cream Sandwich).

Como Usar SQLite em Aplicações Android

Este post utiliza o SQLite para demonstrar como manipular informações em um Banco de Dados no Android. Foi criado um projeto para armazenar as informações de livros, tais como titulo, autor, ano de publicação e gênero. O projeto permite visualizar os livros cadastrados, inserir, alterar e excluir livros.

O projeto é composto pelas seguintes classes:
  • Livro: Classe  onde estão definidos os parâmetros do livro.
  • SQLiteHelper: Classe utilizada na criação e atualização do banco de dados.
  • LivroDb: Classe utilizada nas operações de consulta, inclusão e exclusão de livros.
  • Principal: Classe responsável pela tela inicial da aplicação.
  • VisualizarActivity: Classe responsável pela tela que exibe a relação de livros cadastrados na base de dados.
  • AlterarActivity: Classe responsável pela tela de alteração dos dados dos livros.
  • InsertActivity: Classe responsável pela tela de inserção do livro na base dados.
A Classe Livro é utilizada para manipular os parâmetros dos livros. Esta classe possui como parâmetros as mesmas colunas definidas na base de dados e implementa a interface Serializable que permite a serialização e deserialização da classe. Esta funcionalidade é utilizada para passar o objeto lista de livros de um Intent para outro, necessário na classe Principal, quando é necessário navegar da tela principal para a tela de visualização de livros.

public class Livro implements Serializable {
    private static final long serialVersionUID = -8666122216249625117L;
    private int id;
    private String titulo;
    private String autor;
    private String anoPublicacao;
    private String genero;
    private String[] livrosColumns;
    
    public String[] getLivrosColumns() {
        livrosColumns = new String[] {"_id", "titulo", "autor",
                "ano_publicacao", "genero"                
        };
        return livrosColumns;
    }    
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTitulo() {
        return titulo;
    }
    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
    public String getAutor() {
        return autor;
    }
    public void setAutor(String autor) {
        this.autor = autor;
    }
    public String getAnoPublicacao() {
        return anoPublicacao;
    }
    public void setAnoPublicacao(String anoPublicacao) {
        this.anoPublicacao = anoPublicacao;
    }
    public String getGenero() {
        return genero;
    }
    public void setGenero(String genero) {
        this.genero = genero;
    }
}


A Classe SQLiteHelper herda  da classe SQLiteOpenHelper e possui os métodos OnCreate(SQLiteDatabase) que cria a base de dados juntamente com a tabela livros e o método OnUpgrade(SQLiteDatabase, int, int) que faz a atualização da base de dados SQLite no caso de uma alteração no metadata da base, por exemplo em uma situação de criação de uma nova coluna.

public class SQLiteHelper extends SQLiteOpenHelper {
    public static final String TAG = "SQLiteHelper";
    public static final String DATABASE_NAME = "livros";
    public static final int DATABASE_VERSION = 1;
    private String scriptSQLCreate;
    private String scriptSQLDelete;
    
    public SQLiteHelper(Context context, String databaseName, 
            int databaseVersion, String scriptSQLCreate, 
            String scriptSQLDelete){
        super(context, databaseName, null, databaseVersion);
        this.scriptSQLCreate = scriptSQLCreate;
        this.scriptSQLDelete = scriptSQLDelete;    
    }

    /**
     * Cria a base de dados.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        try {
            db.execSQL(scriptSQLCreate);
        } catch (SQLException e) {
            Log.e(TAG, e.toString());            
        }
    }

    /**
     * Faz o upgrade da base de dados.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, 
            int newVersion) {
        try {
            // Exclui a tabela anterior
            db.execSQL(scriptSQLDelete);
            // Cria a nova tabela
            onCreate(db);
        } catch (SQLException e) {
            Log.e(TAG, e.toString());
        }
    }
}


A classe LivroDb  é composta pelos métodos:
  • LivroDb(Context): Construtor da classe que cria ou abre a base de dados se a mesma já foi criada.
  • close(): Fecha a base de dados se estiver aberta.
  • selectLivros(): Faz um busca na tabela livros retornando uma lista dos livros existentes na base de dados.
  • contentLivro(Livro): Utiliza a classe ContentValues para associar a coluna da tabela livros com o seu respectivo valor.
  • insertLivro(Livro): Insere um livro na base de dados.
  • excluirLivro(Livro): Exclui um livro da base de dados.
  • alterarLivro(Livro): Altera os dados do livro na base de dados.
public class LivroDb{
    private static final String TAG = "LivroDb";
    private static final String SCRIPT_DELETE_DATABASE = 
        "DROP TABLE IF EXISTS livros";

    private static final String SCRIPT_CREATE_DATABASE =
        "CREATE TABLE livros (" +
        "_id INTEGER NOT NULL PRIMARY KEY," + 
        "titulo TEXT," + 
        "autor TEXT," + 
        "ano_publicacao TEXT," + 
        "genero TEXT)";
    private static final String TABLE_NAME = "livros";
    private SQLiteHelper dbHelper;
    private SQLiteDatabase db;
    private List<Livro> livros;    
    
    public LivroDb(Context ctx){
        try {
            dbHelper = new SQLiteHelper(ctx, SQLiteHelper.DATABASE_NAME,
                    SQLiteHelper.DATABASE_VERSION, 
                    LivroDb.SCRIPT_CREATE_DATABASE,
                    LivroDb.SCRIPT_DELETE_DATABASE);

            livros = new ArrayList<Livro>();
            
            // Cria ou abre a base de dados
            db = dbHelper.getWritableDatabase();
            
        } catch (SQLiteException e) {
            Log.e(TAG, e.toString());
        }

    }
    
    public void close(){
        if (db != null) {
            if (db.isOpen() == true) {
                db.close();
            }
        }
    }
    
    public List<Livro> selectLivros(){
        Cursor cursor = null;
        livros.clear();
        try {
            cursor = db.query(LivroDb.TABLE_NAME, new Livro().getLivrosColumns(), 
                    null, null, null, null, null);
            
            if (cursor.getCount() > 0) {
                while (cursor.moveToNext()) {
                    Livro livro = new Livro();
                    
                    if (!cursor.isNull(cursor.getColumnIndex("_id"))) {
                        livro.setId(cursor.getInt(cursor.getColumnIndex("_id")));
                    }
                    
                    if (!cursor.isNull(cursor.getColumnIndex("titulo"))) {
                        livro.setTitulo(cursor.getString(
                                cursor.getColumnIndex("titulo")));
                    }
                    
                    if (!cursor.isNull(cursor.getColumnIndex("autor"))) {
                        livro.setAutor(cursor.getString(
                                cursor.getColumnIndex("autor")));
                    }
                    
                    if (!cursor.isNull(cursor.getColumnIndex("ano_publicacao"))) {
                        livro.setAnoPublicacao(cursor.getString(
                                cursor.getColumnIndex("ano_publicacao")));
                    }
                    
                    if (!cursor.isNull(cursor.getColumnIndex("genero"))) {
                        livro.setGenero(cursor.getString(
                                cursor.getColumnIndex("genero")));
                    }
                    
                    livros.add(livro);
                }
            }
            
        } catch (SQLiteException e) {
            Log.e(TAG, e.toString());
        }
        finally{
            if (cursor != null) {
                if (!cursor.isClosed()) {
                    cursor.close();
                }
            }
        }        
        return livros;
    }
    
    
    private ContentValues contentLivro(Livro livro){
        ContentValues values = new ContentValues();
        values.put("titulo", livro.getTitulo());
        values.put("autor", livro.getAutor());
        values.put("ano_publicacao", livro.getAnoPublicacao());
        values.put("genero", livro.getGenero());
        return values;
    }
    
    /**
     * Insere um livro.
     * @param livro
     * @return Retorna o id do livro inserido ou -1 se ocorrer
     * erro na inserção do livro.
     */
    public long insertLivro(Livro livro){
        long id = 0;
        
        try {
            ContentValues values = contentLivro(livro);
            id = db.insert(TABLE_NAME, "", values);
            
        } catch (SQLiteException e) {
            Log.e(TAG, e.toString());
        }        
        return id;
    }
    
    /**
     * Exclui um livro.
     * @param titulo Titulo do livro a ser excluído
     * @return
     */
    public boolean excluirLivro(String titulo){
        boolean resultado = false;
        try {
            String where = "titulo=?";
            String[] args = new String[]{titulo};
            
            int num = db.delete(TABLE_NAME, where, args);
            
            // Verifica se o livro foi excluída
            if (num == 1) {
                resultado = true;
            }            
        } catch (SQLiteException e) {
            Log.e(TAG, e.toString());
        }
        
        return resultado;
    }
    
    public boolean alterarLivro(Livro livro){
        boolean resultado = false;
        try {
            
            String where = "_id=?";
            String[] args = new String[]{String.valueOf(livro.getId())};
            
            int num = db.update(TABLE_NAME, contentLivro(livro), where, args);
            
            // Verifica se o livro foi alterado
            if (num == 1) {
                resultado = true;
            }            
        } catch (SQLiteException e) {
            Log.e(TAG, e.toString());
        }
        
        return resultado;
    }    
}


A classe Principal exibe o menu da aplicação em um ListView com os itens Visualizar, Inserir, Alterar,  Excluir. Esta classe é composta pelos métodos:
  • onCreate(Bundle): Inicializa a base de dados e monta o menu.
  • onDestroy(): Fecha a base de dados quando a aplicação é finalizada.
  • onItemClick(AdapterView, View, int, int): Herda da classe ListActivity e trata o evento click do ListView chamando o Intent apropriado de acordo com o item clicado no menu.
public class Principal extends ListActivity implements OnItemClickListener {
    public static LivroDb livros;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Cria ou abre a base de dados se já existe
        livros = new LivroDb(this);
        String[] menu = new String[] {"Visualizar", "Inserir",
                "Alterar", "Excluir"};
        
        ListView listView = getListView();
        listView.setAdapter(new ArrayAdapter<String>(this, 
                android.R.layout.simple_list_item_1, menu));
        listView.setOnItemClickListener(this);
        
    }
    
    @Override
    protected void onDestroy() {
         super.onDestroy();     
         // Fecha a base de dados
         livros.close();
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Serializable listLivros = (Serializable)livros.selectLivros();
        
        switch (position) {
        case 0: // Visualizar 
            Intent intentVisualizar = new Intent(this, VisualizarActivity.class);
            intentVisualizar.putExtra("livros", listLivros);
            intentVisualizar.putExtra("tipo", "visualizar");
            startActivity(intentVisualizar);
            break;

        case 1: // Inserir
            startActivity(new Intent(this, InsertActivity.class));
            break;
        
        case 2: // Alterar
            Intent intentAlterar = new Intent(this, VisualizarActivity.class);
            intentAlterar.putExtra("livros", listLivros);
            intentAlterar.putExtra("tipo", "alterar");
            startActivity(intentAlterar);
            break;
            
        case 3: // Excluir
            Intent intentExcluir = new Intent(this, VisualizarActivity.class);
            intentExcluir.putExtra("livros", listLivros);
            intentExcluir.putExtra("tipo", "excluir");
            startActivity(intentExcluir);
            break;
            
        default:
            break;
        }
    }
}

visualizar

A classe VisualizarActivity cria a tela de visualização da relação de livros cadastrados na base de dados. Esta tela além de exibir os livros cadastrados na base de dados, também é utilizada para permitir que o usuário selecione um livro para fazer alteração dos dados ou exclua o livro selecionado. Possui os métodos:
  • onCreate(Bundle): Inicializa as variáveis.
  • onResume(): Exibe a relação de livros.
  • onItemClick(AdapterView, View, int, long): Exibe a tela para alterar ou exclui o livro selecionado.
  • onDestroy(): Fecha a base de dados.
public class VisualizarActivity extends ListActivity implements OnItemClickListener {
    private ListView listView;
    private String tipo; // tipo de activity a ser chamada
    private LivroDb livroDb;
    List<Livro> livros;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        livroDb = new LivroDb(this);        
        listView = getListView();
        listView.setOnItemClickListener(this);
    
    }
    
    @SuppressWarnings("unchecked")
    @Override
    protected void onResume() {
        super.onResume();        
        
        String[] titulos = null;
        
        Intent intent = getIntent();
        
        if (intent != null) {
            livros = (ArrayList<Livro>) intent.getSerializableExtra("livros");
            
            titulos = new String[livros.size()];
            int i = 0;
            for (Livro livro : livros) {
                titulos[i] = livro.getTitulo();
                i++;
            }
            
            setListAdapter(new ArrayAdapter<String>(this,
                    android.R.layout.simple_list_item_1, titulos));

            // Define o tipo de Activity a ser chamada
            tipo = intent.getStringExtra("tipo");
            
            if (tipo.equals("alterar")) {
                setTitle("Selecione o livro para alterar");
            }else if (tipo.equals("excluir")) {
                setTitle("Selecione o livro para excluir");
            }
        }
    }
    
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        
        if (tipo.equals("alterar")) {
            
            // Armazena as informações do livro selecionado
            Livro livro = livros.get(position);
            // Chama a tela de Alterar passando as informações do livro
            // como parâmetro
            Intent intent = new Intent(this, AlterarActivity.class);
            intent.putExtra("livro", livro);
            startActivity(intent);
        }
        
        if (tipo.equals("excluir")) {
            String titulo = (String) listView.getItemAtPosition(position);
            boolean excluiuLivro = livroDb.excluirLivro(titulo);
            
            if (excluiuLivro) {
                Toast.makeText(this, "O livro " + titulo + " foi excluído com sucesso",
                        Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(this, "O livro " + titulo + " não foi excluído com sucesso",
                        Toast.LENGTH_SHORT).show();
            }
        }
        finish();
    }
        
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (livroDb != null) {
            livroDb.close();
        }
    }

}
visualizar

A classe InsertActivity cria a tela utilizada para inserir o livro. Possui os métodos:
  • onCreate(Bundle): Monta a tela de inserção dos dados do livro.
  • onClick(View): Insere os dados do livro no base de dados. Método chamado quando o botão Inserir é clicado.
  • onDestroy(): Fecha a base de dados.
public class InsertActivity extends Activity implements OnClickListener {
    private EditText editTitulo;
    private EditText editAutor;
    private EditText editAno;
    private EditText editGenero;
    private Button buttonInserir;
    private LivroDb livroDb;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.insere_altera);
        setTitle("Inserir Livro");
        editTitulo = (EditText)findViewById(R.id.editTitulo);
        editAutor = (EditText)findViewById(R.id.editAutor);
        editAno = (EditText)findViewById(R.id.editAno);
        editGenero = (EditText)findViewById(R.id.editGenero);
        buttonInserir = (Button)findViewById(R.id.buttonInserirAlterar);
        buttonInserir.setText("Inserir");
        
        buttonInserir.setOnClickListener(this);
        
    }

    @Override
    public void onClick(View arg0) {
        Livro livro = new Livro();
        livroDb = new LivroDb(this);
        long id = 0;
        
        livro.setTitulo(editTitulo.getText().toString());
        livro.setAutor(editAutor.getText().toString());
        livro.setAnoPublicacao(editAno.getText().toString());
        livro.setGenero(editGenero.getText().toString());
        
        id = livroDb.insertLivro(livro);
        
        if (id != -1) {
            Toast.makeText(this, "O livro foi inserido com sucesso.", Toast.LENGTH_LONG).show();
        }
        
        livroDb.close();
        
        finish();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (livroDb != null) {
            livroDb.close();
        }
    }
inserir

A classe AlterarActivity altera os dados do livro já cadastrado na base de dados. Possui os métodos:
  • onCreate(Bundle): Monta a tela com os dados do livro cadastrados na base de dados, permitindo que o usuário faça as alterações necessárias e salve na base de dados.
  • onClick(View): Faz a alteração das informações do livro na base de dados.
  • onDestroy(): Fecha a base de dados.
public class AlterarActivity extends Activity implements OnClickListener {
    private EditText editTitulo;
    private EditText editAutor;
    private EditText editAno;
    private EditText editGenero;
    private Button buttonAlterar;
    private LivroDb livroDb;
    private Livro livro;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.insere_altera);
        setTitle("Alterar livro");
        editTitulo = (EditText)findViewById(R.id.editTitulo);
        editAutor = (EditText)findViewById(R.id.editAutor);
        editAno = (EditText)findViewById(R.id.editAno);
        editGenero = (EditText)findViewById(R.id.editGenero);
        buttonAlterar = (Button)findViewById(R.id.buttonInserirAlterar);
        buttonAlterar.setText("Alterar");
        buttonAlterar.setOnClickListener(this);
        
        Intent intent = getIntent();
        if (intent != null) {
            livro = (Livro) intent.getSerializableExtra("livro");
            // Exibe as informações do livro selecionado
            editTitulo.setText(livro.getTitulo());
            editAutor.setText(livro.getAutor());
            editAno.setText(livro.getAnoPublicacao());
            editGenero.setText(livro.getGenero());
        }
    }

    @Override
    public void onClick(View v) {
        livroDb = new LivroDb(this);
        boolean resultado = false;
        
        Livro livroAlterado = new Livro();
        // O id do livro com os dados alterados permanece o mesmo
        livroAlterado.setId(livro.getId());
        livroAlterado.setTitulo(editTitulo.getText().toString());
        livroAlterado.setAutor(editAutor.getText().toString());
        livroAlterado.setAnoPublicacao(editAno.getText().toString());
        livroAlterado.setGenero(editGenero.getText().toString());
        
        resultado = livroDb.alterarLivro(livroAlterado);
        
        if (resultado == true) {
            Toast.makeText(this, "O livro foi alterado com sucesso.", Toast.LENGTH_LONG).show();
        }
        
        livroDb.close();
        
        finish();        
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (livroDb != null) {
            livroDb.close();
        }
    }
}

alterar2

O arquivo da base de dados livros, pode ser obtido no diretório data/data/br.com.exemplo.sqlite/database através do emulador do Android no Eclipse.

diretorio

As classes não detalhadas no post bem como o projeto podem ser obtidos aqui
O projeto foi desenvolvido com a API 8 do Android e testado no emulador com Android versão 2.2.

Post Relacionado: