Corso programmazione Android Lezione 7: Correzioni, activity di dettaglio con TabLayout
Le altre lezioni del Corso di Programmazione Android sono reperibili a questo indirizzo
In questa lezione oltre ad apportare alcune correzioni, continueremo a lavorare con i database e creeremo una nuova Activity di dettaglio utilizzando il componente TabLayout.
Il codice sorgente mostrato in questa lezione è reperibile qui.
Pratica/Correzioni
Utilizzando l’applicazione fin qui sviluppata possiamo notare alcuni problemi/bug.
Problema 1: Alla rotazione del dispositivo veniamo riportati alla Homepage
Come abbiamo appreso nelle precedenti lezioni, al momento della rotazione del dispositivo l’Activity in primo piano viene riavviata per adattarsi al nuovo tipo di visualizzazione. Quindi tutti i metodi di callback vengono richiamati compreso onCreate() della classe MainActivity. Richiamando onCreate() in questo momento è come se riavviassimo l’intera l’applicazione, per risolvere il nostro problema abbiamo aggiunto delle righe al metodo:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); //modificato instanziata su setSupportActionBar(toolbar); mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout); //Inizializzo il drawer layout nvDrawer = (NavigationView) findViewById(R.id.nvView); //Inizzializzo il Navigation View setupDrawerContent(nvDrawer); //Collego il navigation view al drawer final ActionBar ab = getSupportActionBar(); //Inizializzo la ActionBar ab.setHomeAsUpIndicator(R.mipmap.ic_drawer); //Associo l'icona hamburger ab.setDisplayHomeAsUpEnabled(true); //Faccio si che la zona con l'icona sia clickabile //Analizziamo da qui fragmentClass = HomepageFragment.class; //All'avvio mi serve l'homepage fragment try { fragment = (Fragment) fragmentClass.newInstance(); //Creo una nuova istanza di Fragment } catch (Exception e) { e.printStackTrace(); } //Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); //Creo un'istanza di fragmentmannager fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit(); //Inserisco il fragment nel nosto flContent }
Ci interessa la parte dopo //Analizziamo da qui. Ad ogni creazione dell’Activity abbiamo fatto si che venisse richiamato il fragment principale, questo non è un errore ma dobbiamo essere più precisi. Possiamo risolvere cambiando la parte interessata in:
if (savedInstanceState == null) { //Se non c'è uno stato salvato fragmentClass = HomepageFragment.class; //All'avvio mi serve l'homepage fragment try { fragment = (Fragment) fragmentClass.newInstance(); //Creo una nuova istanza di Fragment } catch (Exception e) { e.printStackTrace(); } // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); //Creo un'istanza di fragmentmannager fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit(); //Inserisco il fragment nel nostro flContent }
Così facendo il fragment principale viene richiamato solo al primo avvio. Ogni volta che eseguiamo l’activity essa salva il suo stato facendo il nostro gioco. Per applicazioni più complesse usando il metodo di callback apposito potremmo personalizzare tale salvataggio.
Problema 2: Quando inseriamo un dato non viene visualizzato subito ma dobbiamo cambiare fragment servendoci del Drawer.
Per inserire i dati all’interno del DB utilizziamo una nuova Activity, come appreso nella lezioni precedenti quando viene aperta una nuova attività la precedente viene messa in background e alla chiusura torna ad essere in foreground ma non viene riavviata (lo stesso ovviamente vale per i Fragment). Per risolvere questo problema dobbiamo utilizzare il metodo di callback onResume() richiamato quando l’Activity torna in primo piano.
All’interno del metodo di callback onResume() dobbiamo ricaricare ripetere la query sul db e ricaricare il cursore e adapter.
@Override public void onResume() { super.onResume(); db.open(); c_resume = db.fetchByTipologia(getResources().getString(R.string.libri)); cur_resume = new SimpleCursorAdapter( getActivity(), android.R.layout.simple_list_item_2, c_resume, new String[] {DatabaseBeReader.BeReaderMetaData.TITOLO, DatabaseBeReader.BeReaderMetaData.AUTORE}, new int[]{android.R.id.text1 , android.R.id.text2}, 0); libriLV.setAdapter(cur_resume); db.close(); }
Ovviamente prima dobbiamo aver definito i nuovi oggetti, all’interno della classe:
public class LibriFragment extends Fragment{ private ListView libriLV; DatabaseBeReader db; Cursor c, c_resume; SimpleCursorAdapter cur, cur_resume;
Potevamo ovviamente usare gli stessi cursor e adapter ma ho preferito puntare sulla leggibilità.
Conclusioni teoriche pratiche
Questi problemi ci hanno insegnato che è bene tenere sempre a mente il ciclo di vita delle activity
Pratica
Nuova Activity di dettaglio
Continueremo a lavorare con i Database e nel creare una nuova Activity vedremo come creare un TabLayout.
Nella lezione precedente abbiamo creato una ListView e abbiamo caricato al suo interno il contenuto di una query eseguita sul nostro Database, adesso dobbiamo fare in modo di poter vedere tutti i campi della nostra registrazione e non solo una piccola selezione. Per farlo ci servirà un’activity di dettaglio implementiamo un OnItemClickListener sulla nostra ListView per aprirla
libriLV.setAdapter(cur); libriLV.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { db.open(); Intent intent = new Intent(getActivity(), VisualizzaLibri.class); //apro una nuova activity String pkg = getActivity().getPackageName(); //Passo come parametro alla nuova Activity l'id della registrazione intent.putExtra(pkg+".ID", c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.ID))); startActivity(intent); } });
Siamo andati oltre poiché al lancio della nuova Activity dovremmo fare una query by id è necessario creare un’activity passando come parametro l’id del record.
Layout della nuova Activity con TabLayout
Mostrare solo i dati del record non è divertente quindi creiamo un’Activity più complessa con 3 tab che conterranno:
- I dati estrapolati dal db
- La copertina del libro (ancora non so dove andare a prenderla [EDIT la versione finale dell’app riporta i prestiti in questo tab])
- Una descrizione del libro presa da Wikipedia
Per non mettere troppa carne al fuoco per oggi pensemo solo al primo tab. Ecco il layout della nostra Activity.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_content" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.begeekmyfriend.bereader.VisualizzaLibri"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/appbar_padding_top" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:popupTheme="@style/AppTheme.PopupOverlay"> </android.support.v7.widget.Toolbar> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> </android.support.design.widget.AppBarLayout> <android.support.v4.view.ViewPager android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout>
Analizziamo il Layout in dettaglio.
Creiamo un Coordinator Layout inseriamo un AppBarLayout (il quale contiene a sua volta una toobar e TabLayout) e un ViewPager. Il ViewPager si occuperà di contenere il Fragment relativo al nostro tab.
Codice Java
Ecco il codice come sempre commentato della nostra classe VisualizzaLibri
package com.begeekmyfriend.bereader; import android.content.Intent; import android.support.design.widget.TabLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.os.Bundle; public class VisualizzaLibri extends AppCompatActivity { private SectionsPagerAdapter mSectionsPagerAdapter; private ViewPager mViewPager; String id_db; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_visualizza_libri); Intent intent = getIntent(); String pkg = getPackageName(); id_db = intent.getStringExtra(pkg + ".ID"); //Recupero quanto passato come argomento all'activity Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); //Pulsante di navigazione clickabile mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); //instazio un oggetto per l'adapter // Set up the ViewPager with the sections adapter. mViewPager = (ViewPager) findViewById(R.id.container); //inizializzo il pager mViewPager.setAdapter(mSectionsPagerAdapter); //associo l'adapter al pager TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); //inizializzo i tab tabLayout.setupWithViewPager(mViewPager); //e gli associo i pager } public class SectionsPagerAdapter extends FragmentPagerAdapter { //classe interna per l'adapter public SectionsPagerAdapter(FragmentManager fm) { //costruttore super(fm); } @Override public Fragment getItem(int position) { //in base alla tab apro un nuovo fragment Fragment frag_tab = null; Bundle args = new Bundle(); switch (position) { case 0: frag_tab = new VisLibriTab1(); args.putString("ID", id_db); //passo argomenti al Fragment frag_tab.setArguments(args); break; case 1: frag_tab = new VisLibriTab2(); break; case 2: frag_tab = new VisLibriTab3(); break; } return frag_tab; } @Override public int getCount() { //Voglio 3 TAb // Show 3 total pages. return 3; } @Override public CharSequence getPageTitle(int position) { //Titolo dei tab switch (position) { case 0: return getResources().getString(R.string.libro); case 1: return getResources().getString(R.string.copertina); case 2: return getResources().getString(R.string.descrizione); } return null; } } }
Analizziamo la classe principale
public class VisualizzaLibri extends AppCompatActivity { private SectionsPagerAdapter mSectionsPagerAdapter; private ViewPager mViewPager; String id_db; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_visualizza_libri); Intent intent = getIntent(); String pkg = getPackageName(); id_db = intent.getStringExtra(pkg + ".ID"); //Recupero quanto passato come argomento all'activity Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); //Pulsante di navigazione clickabile mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); //istanzio un oggetto per l'adapter // Set up the ViewPager with the sections adapter. mViewPager = (ViewPager) findViewById(R.id.container); //inizializzo il pager mViewPager.setAdapter(mSectionsPagerAdapter); //associo l'adapter al pager TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); //inizializzo i tab tabLayout.setupWithViewPager(mViewPager); //e gli associo i pager }
In questo metodo come sempre inizializziamo tutti i componenti base della nostra interfaccia. Le istruzioni seguenti permettono di recuperare quanto passato all’Activity:
Intent intent = getIntent(); String pkg = getPackageName(); id_db = intent.getStringExtra(pkg + ".ID"); //Recupero quanto passato come argomento all'activity
Occupiamoci dell’adapter:
public class SectionsPagerAdapter extends FragmentPagerAdapter { //classe interna per l'adapter public SectionsPagerAdapter(FragmentManager fm) { //costruttore super(fm); } @Override public Fragment getItem(int position) { //i base alla tab apro un nuovo fragment Fragment frag_tab = null; Bundle args = new Bundle(); switch (position) { case 0: frag_tab = new VisLibriTab1(); args.putString("ID", id_db); //passo argomenti al Fragment frag_tab.setArguments(args); break; case 1: frag_tab = new VisLibriTab2(); break; case 2: frag_tab = new VisLibriTab3(); break; } return frag_tab; } @Override public int getCount() { //Voglio 3 TAb // Show 3 total pages. return 3; } @Override public CharSequence getPageTitle(int position) { //Titolo dei tab switch (position) { case 0: return getResources().getString(R.string.libro); case 1: return getResources().getString(R.string.copertina); case 2: return getResources().getString(R.string.descrizione); } return null; } }
Nel nostro Adapter xi occupiamo di gestire i Fragment specifici per i tab, il numero e il titolo.
La nostra activity ha il seguente aspetto:
Il fragment VisLibriTab1, Visualizziamo tutti i campi del DB
Adesso che abbiamo costruito l’interfaccia della nostra Activity occupiamoci del Fragment principale.
Layout
Vediamo il Layout
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/titolo_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/titolo"/> <TextView android:id="@+id/titolo_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/autore_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/autore"/> <TextView android:id="@+id/autore_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/formato_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/formato"/> <TextView android:id="@+id/formato_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/genere_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/genere"/> <TextView android:id="@+id/genere_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/posizione_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/posizione"/> <TextView android:id="@+id/posizione_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/prestato_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/prestato"/> <TextView android:id="@+id/prestato_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/prestato_chi_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/prestato_chi"/> <TextView android:id="@+id/prestato_chi_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/prestato_quando_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/prestato_quando"/> <TextView android:id="@+id/prestato_quando_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> <android.support.design.widget.FloatingActionButton android:id="@+id/fb_delete" android:src="@mipmap/ic_action_discard" app:fabSize="normal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_marginBottom="65dp" android:layout_marginEnd="10dp" /> </android.support.design.widget.CoordinatorLayout>
Non serve entrare troppo nel dettaglio, i componenti sono semplici e li conosciamo. Abbiamo creato una serie di TextView (dall’aspetto orribile direi) e abbiamo preparato un pulsante per la cancellazione del record.
Codice Java
Passiamo al codice Java
package com.begeekmyfriend.bereader; import android.database.Cursor; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; /** * Created by fabrizio on 17/04/16. */ public class VisLibriTab1 extends Fragment { private static final String ARG_SECTION_NUMBER = "section_number"; DatabaseBeReader db; Cursor c; String id_db; TextView titolo_tv, autore_tv, formato_tv, genere_tv, posizione_tv, prestato_tv, prestato_chi_tv, prestato_quando_tv; FloatingActionButton fab_del; public VisLibriTab1() { //Inutile costruttore di default } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); View rootView = inflater.inflate(R.layout.fragment_vis_tab1, container, false); Bundle args = getArguments(); id_db = args.getString("ID"); //Recuperiamo l'id passatoci dall'Activity fab_del = (FloatingActionButton) rootView.findViewById(R.id.fb_delete); fab_del.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { db.open(); db.cancellaById(id_db); //richiamo il metodo di cancellazione del record getActivity().finish(); //chiudo l'activity } }); //Inizializzo tutti i TetxVew adibiti al contenimento dei dati titolo_tv = (TextView) rootView.findViewById(R.id.titolo_tv); autore_tv = (TextView) rootView.findViewById(R.id.autore_tv); formato_tv = (TextView) rootView.findViewById(R.id.formato_tv); genere_tv = (TextView) rootView.findViewById(R.id.genere_tv); posizione_tv = (TextView) rootView.findViewById(R.id.posizione_tv); prestato_tv = (TextView) rootView.findViewById(R.id.prestato_tv); prestato_chi_tv = (TextView) rootView.findViewById(R.id.prestato_chi_tv); prestato_quando_tv = (TextView) rootView.findViewById(R.id.prestato_quando_tv); db = new DatabaseBeReader(getActivity()); db.open(); c = db.fetchById(id_db); //Lancio la query By id if ( c.getCount() > 0) //Dopo aver aperto il db inizializza i componenti { while (c.moveToNext()) { //anche se ho sicuramente un solo recordo mi serve un ciclo //scrivo il valore del campo del recordo all'interno della TextView titolo_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.TITOLO))); autore_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.AUTORE))); formato_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.FORMATO))); genere_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.GENERE))); posizione_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.POSIZIONE))); prestato_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO))); prestato_chi_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO_CHI))); prestato_quando_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO_QUANDO))); } } db.close(); //chiudo la connessione return rootView; } }
L’unica parte che non conosciamo è quella relativa alla lettura del cursore riga per riga
c = db.fetchById(id_db); //Lancio la query By id if ( c.getCount() > 0) //Dopo aver aperto il db inizializza i componenti { while (c.moveToNext()) { //anche se ho sicuramente un solo recordo mi serve un ciclo //scrivo il valore del campo del recordo all'interno della TextView titolo_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.TITOLO))); autore_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.AUTORE))); formato_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.FORMATO))); genere_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.GENERE))); posizione_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.POSIZIONE))); prestato_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO))); prestato_chi_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO_CHI))); prestato_quando_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO_QUANDO))); } }
Ecco l’orribile aspetto del nostro Tab:
Gli altri Tab
I fragment per gli altri Tab al momento sono vuoti (e identici)
Ecco layout e codice sorgente
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.design.widget.CoordinatorLayout>
package com.begeekmyfriend.bereader; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class VisLibriTab2 extends Fragment { public VisLibriTab2() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); View rootView = inflater.inflate(R.layout.fragment_vis_libri_tab2, container, false); // Inflate the layout for this fragment return rootView; } }
Nella prossima lezione vedremo di migliore l’aspetto di alcuni componenti della nostra applicazione.
Originariamente pubblicato su Be Geek My Friend