André Alves de Lima

Talking about Software Development and more…

Aplicações Android com Xamarin – Parte 6 de N – DatePicker e Spinner (entre outras pendências)

No meu último artigo da série sobre desenvolvimento Android com Xamarin Native, nós terminamos com uma lista de pendências que deveriam ser resolvidas antes de continuarmos com o resto da série. Muitos dos itens são ajustes gerais, como o posicionamento de alguns botões (que deveriam estar na parte inferior da tela e não logo abaixo dos controles), refactoring para otimizar algumas partes do código, etc. Porém, além desses ajustes gerais, temos dois novos conceitos muito importantes que utilizaremos em praticamente toda aplicação que formos construir: DatePicker e Spinner (ComboBox).

Como o próprio nome já diz, o DatePicker serve para selecionarmos uma data. Obviamente, quando temos um campo de data na nossa aplicação, nós não vamos deixar o usuário digitá-la manualmente, uma vez que isso pode acarretar vários erros de digitação (além de oferecer uma péssima experiência de uso). Já o Spinner é praticamente um ComboBox (caixa de seleção), que devemos utilizar quando o usuário tem várias opções de seleção para um determinado campo.

Quer ver como é que ficaram as alterações, além da adição do DatePicker e Spinner? Então vamos lá!

Acesse os artigos anteriores

Antes de continuar com a leitura deste artigo, acesse os artigos anteriores dessa série utilizando os links abaixo:

Parte 1: Prototipando a aplicação
Parte 2: Ambiente Xamarin Android e Hello World
Parte 3: Classes de modelo e primeira tela
Parte 4: Customizando o controle ListView
Parte 5: Navegação entre telas

Antes de começarmos, um aviso…

Infelizmente a estrutura desse artigo ficará um pouquinho mais complicada do que você está acostumado a ver por aqui. O motivo disso é que eu não posso simplesmente chegar aqui e explicar a utilização do DatePicker e Spinner (que são os assuntos principais desse artigo). Como estamos no meio de uma série, eu tenho que mostrar e explicar todas as alterações que foram feitas na aplicação desde a última publicação.

Dito isso, eu vou abordar aqui no artigo as alterações que eu fiz na aplicação seguindo a mesma ordem que eu utilizei na implementação. Caso você queira ver exatamente o código dessas alterações (versões antes e depois), dá uma olhadinha nestes meus commits no GitHub:

Se você não estiver seguindo a série inteira e quiser pular diretamente para os conceitos novos deste artigo, continue lendo a partir da seção “DatePicker para seleção de datas“.

Otimizando ListViewGroup mantendo referência para os gastos

A primeira alteração que eu fiz no projeto foi a otimização do ListViewGroup. Na versão dos artigos anteriores, eu criei duas classes para alimentarmos o nosso ListView: a classe ListViewGroup (para os agrupamentos por data) e a classe ListViewItem (que tinha os detalhes de cada gasto). Porém, a segunda classe é completamente desnecessária. Ao invés de termos que duplicar os dados dos gastos nessa classe “ListViewItem“, nós podemos eliminá-la completamente e referenciarmos diretamente os gastos como filhos na classe ListViewGroup.

Ou seja, na classe ListViewGroup, ao invés de termos uma lista de ListViewItems, nós teremos uma lista de gastos. Veja o antes e o depois:

Se você quiser copiar o novo código em formato texto, aqui vai ele:

    public class ListViewGroup
    {
        public DateTime Data { get; set; }
        public List<Models.Gasto> Gastos { get; set; }

        public ListViewGroup()
        {
            Gastos = new List<Models.Gasto>();
        }
    }

Obviamente, uma vez efetuada essa alteração, se tentarmos compilar a aplicação nós receberemos vários erros. Vamos consertá-los um de cada vez, de forma que consigamos ter o mesmo comportamento da versão anterior da aplicação.

O próximo local de ajuste será na classe ListViewAdapter, mais especificamente nos métodos “GetChildrenCount” e “GetChildView“. Como estamos trabalhando diretamente com os gastos no ListViewGroup, teremos que fazer alguns pequenos ajustes no código desses métodos:

        public override int GetChildrenCount(int groupPosition)
        {
            return grupos[groupPosition].Gastos.Count;
        }

        public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
        {
            View view = convertView;
            var gasto = grupos[groupPosition].Gastos[childPosition];

            if (view == null)
            {
                view = context.LayoutInflater.Inflate(Resource.Layout.ListItemRow, null);
            }
            view.FindViewById<TextView>(Resource.Id.Valor).Text = string.Format("{0:c}", gasto.Valor);
            view.FindViewById<TextView>(Resource.Id.NomeEstabelecimento).Text = gasto.Estabelecimento.Nome;

            return view;
        }

Em seguida, no método “ListViewGastos_ChildClick” da MainActivity, nós temos que fazer o mesmo estilo de alteração:

        private void ListViewGastos_ChildClick(object sender, ExpandableListView.ChildClickEventArgs e)
        {
            var infoGrupo = _listViewGroups[e.GroupPosition];
            var gasto = infoGrupo.Gastos[e.ChildPosition];
            var intent = new Intent(this, typeof(EditarGastoActivity));
            intent.PutExtra("Id", gasto.Id);
            intent.PutExtra("Data", infoGrupo.Data.Ticks);
            intent.PutExtra("Estabelecimento", gasto.Estabelecimento.Nome);
            intent.PutExtra("Valor", Convert.ToDouble(gasto.Valor));
            StartActivityForResult(intent, 0);
        }

Outra alteração óbvia é no método “PrepararListViewGroups“, que faz a criação dos ListViewGroups baseando-se na lista de gastos. Nesse método, ao invés de criarmos os ListViewItems, nós temos que adicionar os gastos diretamente no ListViewGroup (utilizando o método “AddRange” passando a lista de gastos do agrupamento atual):

                foreach (var gastoAgrupado in gastosAgrupados)
                {
                    var listViewGroup = new ListViewGroup() { Data = gastoAgrupado.Data };
                    listViewGroups.Add(listViewGroup);
                    listViewGroup.Gastos.AddRange(gastoAgrupado.Gastos);
                }

Por fim, no método “OnActivityResult” da MainActivity (que é o método que recebe e processa a resposta da tela de edição de gastos), nós não precisamos mais nos preocupar em atualizar as informações dos ListViewItems, uma vez que são os próprios gastos que são filhos do ListViewGroup. Dessa forma, nós só precisamos chamar o método “NotifyDataSetChanged” do ListViewAdapter para que as novas informações sejam exibidas no ListView:

Botões de Salvar/Cancelar na parte inferior da tela

A segunda alteração que eu fiz no projeto foi com relação ao posicionamento dos botões “Salvar” e “Cancelar” na tela de edição de gastos. Como podemos observar no protótipo que desenhamos para a nossa aplicação, esses botões devem ser posicionados na parte inferior da tela, e não logo abaixo dos controles do formulário.

Essa alteração não foi tão difícil de ser feita. Eu só precisei adicionar um outro LinearLayout englobando os editores (TextViews e EditTexts para edição de data, estabelecimento e valor) e, além disso, eu defini a gravidade do LinearLayout dos botões como “bottom“, aí os botões foram parar na parte inferior da tela:

Este é novo código completo do layout da tela de edição de gastos, contemplando as alterações mencionadas acima:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical"
        android:padding="10dp">
        <TextView
            android:text="Data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textViewData" />
        <EditText
            android:inputType="date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editTextData" />
        <TextView
            android:text="Estabelecimento"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textViewEstabelecimento" />
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editTextEstabelecimento"
            android:inputType="text" />
        <TextView
            android:text="Valor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textViewValor" />
        <EditText
            android:inputType="numberDecimal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editTextValor" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:weightSum="2">
        <Button
            android:text="Salvar"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonSalvar"
            android:layout_weight="1" />
        <Button
            android:text="Cancelar"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonCancelar"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

Suporte à alteração de datas

Uma limitação que tínhamos na última versão da aplicação é que nós não conseguíamos mudar a data de um gasto. Ou melhor, nós até conseguíamos entrar com uma nova data na tela de edição de gastos, porém essa data não era respeitada na hora de salvar as alterações. Eu deixei para resolver essa limitação neste outro artigo porque nós precisávamos fazer primeiro o refactoring da classe ListViewGroup (como vimos anteriormente). Agora que nós já fizemos esse ajuste, fica mais fácil de implementarmos o suporte à alteração de datas dos gastos, uma vez que nós só precisamos tirar o gasto de um ListViewGroup e colocar no outro ListViewGroup correspondente à nova data.

A primeira coisa que nós temos que fazer é criarmos um método que fará a expansão da ListView. Nós já temos esse código pronto dentro do método “OnCreate” da MainActivity, porém, como nós precisaremos dessa mesma funcionalidade em outro lugar, é melhor extrairmos esse código em um método separado dentro de MainActivity:

        private void ExpandirTodosOsGruposDoListView()
        {
            var listViewGastos = FindViewById<ExpandableListView>(Resource.Id.listViewGastos);
            for (int group = 0; group <= _adapter.GroupCount - 1; group++)
            {
                listViewGastos.ExpandGroup(group);
            }
        }

Em seguida, nós ajustamos o método “OnCreate” da MainActivity de maneira que ele chame esse método:

            ExpandirTodosOsGruposDoListView();
            listViewGastos.ChildClick += ListViewGastos_ChildClick;

Agora sim nós podemos ajustar o método “OnActivityResult” para implementarmos o suporte à alteração de datas dos gastos. Quando recebermos as informações da tela de edição de gastos, nós verificaremos se a data do gasto foi alterada. Caso positivo, nós removemos o gasto do ListViewGroup anterior, encontramos o ListViewGroup destino (ou criamos um novo, caso um ListViewGroup correspondente à nova data ainda não exista) e adicionamos o gasto nele.

Eu sei que essa explicação ficou um pouco complicada, mas talvez com o código você consiga entender melhor o que eu estou querendo dizer:

                if (gasto != null)
                {
                    var dataAnterior = gasto.Data.Date;
                    var dataNova = new DateTime(data.Extras.GetLong("Data")).Date;
                    gasto.Data = dataNova;
                    gasto.Valor = Convert.ToDecimal(data.Extras.GetDouble("Valor"));
                    var nomeEstabelecimento = data.Extras.GetString("Estabelecimento");
                    var estabelecimento = _estabelecimentos.FirstOrDefault(e => string.Compare(e.Nome, nomeEstabelecimento, StringComparison.InvariantCultureIgnoreCase) == 0);
                    if (estabelecimento == null)
                    {
                        estabelecimento = new Models.Estabelecimento()
                        {
                            Nome = nomeEstabelecimento
                        };
                    }
                    gasto.Estabelecimento = estabelecimento;

                    if (!dataAnterior.Equals(dataNova))
                    {
                        var listViewGroupAnterior = _listViewGroups.First(lvg => lvg.Data.Equals(dataAnterior));
                        listViewGroupAnterior.Gastos.Remove(gasto);

                        var listViewGroupNovo = _listViewGroups.FirstOrDefault(lvg => lvg.Data.Equals(dataNova));
                        if (listViewGroupNovo == null)
                        {
                            listViewGroupNovo = new ListViewGroup()
                            {
                                Data = dataNova
                            };
                            _listViewGroups.Add(listViewGroupNovo);
                        }
                        listViewGroupNovo.Gastos.Add(gasto);
                    }

                    _adapter.NotifyDataSetChanged();
                    ExpandirTodosOsGruposDoListView();
                }

ListView como atributo de MainActivity

Uma prática que eu costumo ver em exemplos de projeto Android é a declaração dos controles da tela como atributos da Activity. Eu segui essa prática na tela de edição de gastos, onde eu declarei todos os controles que eu precisei referenciar como atributos da classe EditarGastoActivity. Porém, na MainActivity eu não segui esse padrão e acabei declarando o ListView de gastos tanto dentro do método “OnCreate” quanto dentro do método “ExpandirTodosOsGruposDoListView“.

Antes de prosseguir com as outras alterações, eu resolvi corrigir essa inconsistência, declarando o ListView como um atributo da MainActivity:

DatePicker para seleção de datas

Agora sim chegamos à parte mais interessante deste artigo. Chega de refactoring, chega de ajustes complicados de se entender. Tomando como base este artigo do site da própria Xamarin, vamos aprender a utilizar um DatePicker para escolhermos a data na tela de edição de gastos!

Para implementarmos seleção de datas no Xamarin Android Native, nós temos que adicionar uma nova classe que herde de “DialogFragment” e “IOnDateSetListener“. Eu sinceramente não sei porque o próprio Android ou o próprio Xamarin Android Native já não contém uma implementação dessa classe que é essencial na hora de selecionarmos uma data nas nossas aplicações.

Mas, enfim, como essa implementação não existe nativamente, eu tomei como base o código da classe “DatePickerFragment” apresentado no artigo sobre DatePicker do site da Xamarin e adicionei no nosso projeto com algumas pequenas alterações (que eu vou explicar logo mais):

    public class DatePickerFragment : DialogFragment, DatePickerDialog.IOnDateSetListener
    {
        Action<DateTime> _onDateSelected;
        private DateTime _initialDate;

        public DatePickerFragment(DateTime initialDate, Action<DateTime> onDateSelected)
        {
            this._initialDate = initialDate;
            this._onDateSelected = onDateSelected;
        }

        public override Dialog OnCreateDialog(Bundle savedInstanceState)
        {
            return new DatePickerDialog(
                Activity,
                this,
                _initialDate.Year,
                _initialDate.Month - 1,
                _initialDate.Day);
        }

        public void OnDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
        {
            // Note: monthOfYear is a value between 0 and 11, not 1 and 12!
            DateTime selectedDate = new DateTime(year, monthOfYear + 1, dayOfMonth);
            _onDateSelected(selectedDate);
        }
    }

Note que o código não é tão complicado. Primeiro nós temos dois atributos: um que guardará uma referência para a ação que deverá ser executada quando uma data for selecionada (_onDateSelected) e outro que guardará a informação da data inicial que deve ser selecionada no DatePicker quando ele for exibido (_initialDate). No construtor dessa classe nós só passamos os valores para esses dois atributos via parâmetros.

Em seguida, temos que dar um override no método “OnCreateDialog” (que vem da classe “DialogFragment“). Nesse método nós temos que retornar o diálogo que deve ser utilizado quando o fragmento for exibido. No nosso caso, nós queremos exibir um DatePicker, portanto, nós retornamos uma instância de DatePickerDialog utilizando as informações da data inicial que foi passada por parâmetro no construtor.

Por fim, temos que implementar o método “OnDateSet” (que é definido no contrato da interface “IOnDateSetListener“). Nesse método nós pegamos a data que foi selecionada pelo usuário (atentando-se para o fato que o mês vai de 0 a 11, e não de 1 a 12) e chamamos o delegate “_onDateSelected” passando a data selecionada por parâmetro.

Com essa classe criada, nós podemos exibir um DatePicker quando o usuário clicar no campo “Data” da tela de edição de gastos. Para isso, vamos adicionar um event handler para esse evento no método “OnCreate” da classe “EditarGastoActivity“:

            _editTextData.Click += editTextData_Click;

Em seguida, adicionamos a implementação desse evento:

        private void editTextData_Click(object sender, EventArgs e)
        {
            var frag = new DatePickerFragment(
                Convert.ToDateTime(_editTextData.Text),
                (data) =>
                {
                    _editTextData.Text = data.ToShortDateString();
                });
            frag.Show(FragmentManager, string.Empty);
        }

Como você pode perceber, a exibição do DatePicker se dá de maneira muito simples. Nós só temos que criar uma instância da classe “DatePickerFragment” (que acabamos de criar) passando como parâmetro a data que está selecionada atualmente no editor e, no segundo parâmetro, nós passamos a ação que deverá ser executada quando uma data for selecionada. Nesse caso, nós vamos simplesmente substituir o conteúdo do editor com a nova data selecionada. A última coisa que temos que fazer depois disso é a exibição do DatePicker, que é feita através do método “Show” do fragmento, passando o “FragmentManager” (propriedade da Activity).

Nota: em alguns exemplos que encontrei na internet (inclusive no próprio exemplo do site da Xamarin), os autores utilizaram uma propriedade chamada “TAG” na implementação de “DatePickerFragment”. O valor dessa tag é uma constante qualquer e ele é passado no segundo parâmetro do método “Show” do fragmento. Porém, eu sinceramente não entendi para que serve essa “tag”. Como você percebe no código acima, eu estou passando uma string vazia para o segundo parâmetro do método “Show” e tudo funcionou perfeitamente.

Execute a aplicação e veja só o que acontece quando clicamos na data:

Spinner (ComboBox) para seleção de estabelecimentos

A próxima modificação que faremos no nosso projeto é a utilização de um Spinner (ComboBox) para a seleção dos estabelecimentos. Primeiramente, temos que alterar o tipo do controle no layout da tela de edição de gastos. Ao invés de utilizarmos um EditText, nós vamos utilizar um Spinner. Além disso, para mantermos a consistência, vamos alterar o nome de “editTextEstabelecimento” para “spinnerEstabelecimento“:

Veja como ficou o código do layout após essa alteração:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical"
        android:padding="10dp">
        <TextView
            android:text="Data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textViewData" />
        <EditText
            android:inputType="date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editTextData" />
        <TextView
            android:text="Estabelecimento"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textViewEstabelecimento" />
        <Spinner
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/spinnerEstabelecimento"/>
        <TextView
            android:text="Valor"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textViewValor" />
        <EditText
            android:inputType="numberDecimal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/editTextValor" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:weightSum="2">
        <Button
            android:text="Salvar"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonSalvar"
            android:layout_weight="1" />
        <Button
            android:text="Cancelar"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/buttonCancelar"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

Agora, antes de prosseguirmos com as próximas alterações, nós temos que fazer um pequeno ajuste na MainActivity. Como nós precisaremos da lista de estabelecimentos para alimentarmos o Spinner, vamos alterar a declaração dessa lista na classe MainActivity. Ao invés de trabalharmos com um atributo privado, vamos fazer com que a lista de estabelecimentos seja uma propriedade pública estática. Com essa alteração, nós conseguiremos acessar a lista de estabelecimentos a partir da tela de edição de gastos:

Em seguida, vamos abrir o código da classe “EditarGastoActivity” e, ao invés de declararmos um EditText para o campo “Estabelecimento“, vamos declarar um Spinner:

        private Spinner _spinnerEstabelecimento;

Para alimentarmos um Spinner no Android, nós temos que utilizar um Adapter. O conceito de Adapter foi abordado na parte 3 e na parte 4 dessa série, quando carregamos os gastos no ListView (que também precisa de um Adapter para ser carregado). No caso do Spinner de estabelecimentos, como nós só exibiremos uma lista simples, vamos trabalhar com um ArrayAdapter de strings (que serão os nomes dos estabelecimentos). O layout desse Adapter será o “SimpleSpinnerItem“, que é o layout padrão de Spinners na plataforma Android:

            var nomeEstabelecimento = Intent.Extras.GetString("Estabelecimento");
            _spinnerEstabelecimento = FindViewById<Spinner>(Resource.Id.spinnerEstabelecimento);
            _spinnerEstabelecimento.Adapter = new ArrayAdapter<string>(this, global::Android.Resource.Layout.SimpleSpinnerItem, MainActivity.Estabelecimentos.Select(e => e.Nome).ToArray());
            var estabelecimento = MainActivity.Estabelecimentos.First(e => nomeEstabelecimento == e.Nome);
            _spinnerEstabelecimento.SetSelection(MainActivity.Estabelecimentos.IndexOf(estabelecimento));

Note que nós podemos alterar a seleção do Spinner programaticamente através do método “SetSelection“. O único “porém” desse método é que ele recebe o índice do item que deve ser selecionado, e não o próprio item (que nesse caso seria um estabelecimento).

Por fim, a última alteração que temos que fazer com relação ao Spinner é no método “Salvar_Click“, onde nós temos que adicionar o texto do estabelecimento selecionado no “Intent” que será retornado à tela principal. Antes nós estávamos simplesmente pegando o texto atual do EditText, mas como agora nós estamos trabalhando com uma caixa de seleção, nós temos que pegar o estabelecimento correspondente através da propriedade “SelectedItemPosition” do Spinner:

            intent.PutExtra("Estabelecimento", MainActivity.Estabelecimentos[_spinnerEstabelecimento.SelectedItemPosition].Nome);

E com isso temos o seguinte efeito ao clicarmos no estabelecimento na tela de edição de gastos:

Ordenando os grupos do ListView

Antes de finalizarmos esse artigo, vamos implementar mais duas melhorias no nosso projeto. A primeira delas diz respeito à ordenação dos grupos do ListView. Se você notar, os grupos não estão ordenados por data. Se alterarmos a data de um gasto de maneira que outro grupo seja criado, ele será adicionado sempre no final do ListView.

Para ordenarmos o ListView nós temos que fazer duas alterações muito simples. A primeira delas está na hora de agruparmos os dados no método “PrepararListViewGroups“. Nesse ponto, nós temos que adicionar um “order by” por data na query LINQ:

Outro ponto que temos que alterar é o método “OnActivityResult“, onde adicionaremos uma chamada para o método “Sort” da lista de ListViewGroups antes de atualizarmos o adapter:

E com essas duas alterações, nós garantimos que os grupos do ListView estarão sempre ordenados por data!

Suporte à criação de novos gastos e exclusão de todos os gastos

A segunda e última melhoria que vamos implementar é o suporte à criação de novos gastos e exclusão de todos os gastos. Para isso, vamos adicionar dois botões (“Novo” e “Limpar“) na parte inferior da tela principal da aplicação:

Os botões foram adicionados dentro de um LinearLayout com gravidade “bottom“, exatamente da mesma forma que fizemos anteriormente quando posicionamos os botões da tela de edição de gastos. Veja só como é que ficou o código do layout da tela principal após a adição desses dois botões:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:minWidth="25px"
    android:minHeight="25px">
  <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:orientation="vertical">
    <ExpandableListView
        android:minWidth="25px"
        android:minHeight="25px"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listViewGastos" />
  </LinearLayout>
  <LinearLayout
      android:orientation="horizontal"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom"
      android:weightSum="2">
    <Button
        android:text="Novo"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/buttonNovo"
        android:layout_weight="1" />
    <Button
        android:text="Limpar"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/buttonLimpar"
        android:layout_weight="1" />
  </LinearLayout>
</LinearLayout>

Com os botões criados, vamos declará-los como atributos no contexto da Activity:

        private Button _buttonNovo;
        private Button _buttonLimpar;

Em seguida, vamos instanciá-los e adicionarmos “hooks” para os seus eventos “Click” no método “OnCreate” da MainActivity:

            _buttonNovo = FindViewById<Button>(Resource.Id.buttonNovo);
            _buttonNovo.Click += buttonNovo_Click;

            _buttonLimpar = FindViewById<Button>(Resource.Id.buttonLimpar);
            _buttonLimpar.Click += buttonLimpar_Click;

A implementação dos eventos “Click” desses botões será muito tranquila. Para o botão de “Limpar“, nós temos que limpar a lista de gastos e a lista de ListViewGroups, chamando em seguida o método “NotifyDataSetChanged” do Adapter para que o conteúdo do ListView seja atualizado:

        private void buttonLimpar_Click(object sender, EventArgs e)
        {
            _gastos.Clear();
            _listViewGroups.Clear();
            _adapter.NotifyDataSetChanged();
        }

Já a implementação do evento “Click” do botão “Novo” será também bem simples. Nesse caso, o código será muito parecido com o que aprendemos na parte anterior dessa série onde abrimos a tela de edição de gastos a partir da tela principal (método “ListViewGastos_ChildClick“). Porém, nesse caso nós só vamos passar no “Intent” o valor “-1” para a chave “Id” e a data atual para a chave “Data“:

        private void buttonNovo_Click(object sender, EventArgs e)
        {
            var intent = new Intent(this, typeof(EditarGastoActivity));
            intent.PutExtra("Id", -1);
            intent.PutExtra("Data", DateTime.Now.Date.Ticks);
            StartActivityForResult(intent, 0);
        }

Além disso, vamos nos certificar que o código do método “OnCreate” da tela de edição de gastos não utilize o método “First“, mas sim “FirstOrDefault” na hora de selecionarmos o estabelecimento inicial. Como nós não estamos passando nenhum estabelecimento na hora de criarmos um novo gasto, se utilizarmos o método “First“, nós receberemos um erro porque não temos nenhum estabelecimento com nome vazio:

Por fim, vamos alterar o código do método “OnActivityResult” da classe MainActivity, de maneira que ele faça o tratamento para quando o “Id” é “-1” (nesse caso um novo gasto deve ser adicionado na lista de gastos) e que não acesse diretamente o ListViewGroup anterior com o método “First“, mas sim, com o método “FirstOrDefault“, uma vez que não existe ListViewGroup anterior quando estamos criando um novo gasto:

Pronto! E com isso nós conseguiremos criar novos gastos na nossa aplicação.

Tema do próximo artigo e pendências para artigos futuros

O tema do próximo artigo dessa série será bem interessante. Nós aprenderemos a persistir os dados no dispositivo! Eu não faço ideia de como nós poderemos salvar esses dados, mas acredito que nós tenhamos algumas possibilidades, como salvar um arquivo em disco (xml ou json) ou até mesmo a utilização de um banco de dados SQLite.

Além disso, eu notei que temos algumas novas pendências que devem ser resolvidas em algum momento dessa série:

– Assegurar que Estabelecimento e Valor foram preenchidos na tela de edição de gastos

– Possibilitar a criação de novos estabelecimentos

– Confirmação se queremos mesmo deletar todos os gastos

– Possibilitar a deleção de somente um gasto específico

Acesse o código no GitHub

Normalmente eu disponibilizo os códigos de exemplo dos artigos e vídeos somente para as pessoas que se inscrevem na minha newsletter. Porém, como nessa série nós evoluiremos o projeto em cada artigo, achei mais interessante publicar o código no GitHub, onde todos poderão acompanhar a evolução do projeto quando novos artigos forem sendo publicados. Caso você se interesse, acesse o repositório desse projeto no meu GitHub.

Concluindo

Eu sei que o conteúdo desse artigo pode ter sido um pouco confuso, uma vez que ele conteve bastante refactoring e aperfeiçoamentos na aplicação. Porém, como eu quero fazer com que qualquer pessoa consiga implementar a mesma aplicação junto comigo, não teve jeito. Tive que fazer esses refactorings antes de darmos continuidade aos conceitos mais avançados. De certa forma, é até interessante que você consiga ver exatamente como eu fui evoluindo a aplicação, já que tudo isso é muito novo para mim também.

Além das inúmeras melhorias que fizemos no código da aplicação, no artigo de hoje nós aprendemos a utilizar dois controles muito importantes: DatePicker e Spinner. Com o DatePicker nós conseguimos exibir um seletor de datas. Já com o Spinner, nós conseguimos exibir uma caixa de opções (ComboBox) onde o usuário pode escolher uma entre várias opções disponíveis.

Espero que você tenha conseguido entender todos os conceitos e ajustes apresentados em mais essa parte dessa série sobre desenvolvimento Android com Xamarin Native. Se você ficar com alguma dúvida, é só deixar um comentário no final do artigo.

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *