Moi c'est Edouard
Android 1.0 le 22 octobre 2008
Pas de clavier, pas de multitouch
Android 1.1 en février 2009 Banana Bread
Mise à jour par le réseau
Android 1.5 le 30 avril 2009 CupCake
Clavier tactile
Presse papier
Android 1.6 Donut
Gestion des définition d'écran 1280x720
Fonction de recherche local sur le téléphone
Android 2.0 et 2.1 Eclair
Android 2.1 le 20 mai 2010 Froyo
Android 2.3 le 6 décembre 2010 Gingerbread
Android 3.0 le 22 février 2011 Honeycomb
Android 4.0 le 19 octobre 2011 Ice Cream Sandwich
Android 4.1, 4.2, 4.3 le 9 juillet 2012 Jelly Bean
Android 4.4 le 31 octobre 2013 KitKat
Android 5.0 le 15 octobre 2014 Lollipop
Ce qui ceux dire ?
La fragmentation de device
La fragmentation par marque
La fragmentation par version
Comparons avec IOS
La fragmentation par écran
On avait different projet ou sous projet qui étaient compilé en .jar et réinjecté dans le projet principal
Les modules sont une «unité discrète de fonctionnalité qui peut être exécuté, testé et débogué indépendamment»
Chaque module doit avoir son propre son propre fichier Gradle.
Tout comme Eclipse, ces modules peuvent être des bibliothèque jar.
Android Studio a amélioré la partie designer pour la rendre plus robuste au design complexe et mieux gérer les intéractions entre les différentes vues
Toutes les informations de compilation ou des jointures des modules sont mis dans différents fichiers gradle.
Téléchargement et importation automatique de module/librarie à la compilation.
Contient les outils nécessaire pour créer, compiler et empacter les applications
Est l'outil qui permet de se connecter aux device Android et de les debugger (Breakpoint, stack, watch, log, ...)
Google nous fourni un IDE préféré pour developper les application android basé sous IntelliJ IDE.
Cet IDE regroupe des éditeurs pour le code, le design, la configuration, le debug, etc
Android studio se base sur des fichiers Gradles qui permette de définir comme le projet se fait compiler.
A partir de Android 5.0. ART utilise la compilation Ahead Of Time Compilation. Pendant le processus de déploiement de l'application sur un device android, le code de l'application est traduit en code machine.
Le résultat est que le code compilé est 30% plus gros mais l'application s'execute plus rapidement dès le début
Ce processus sauvegarde de la batterie vu que le code n'est plus traduit en code machine à chaque lancement d'application
Android Studio télécharge les premiers SDK
Comment choisir ?
En fonction de :
Empty Activity
Main Activity
Virtual Device (AVB)
Cliquer sur "Create Virtual Device..."
Virtual Device (AVB)
Virtual Device (AVB)
Virtual Device (AVB)
Lançons l'application !
Votre première application
Félicitation !
Make yourself at home
Les vues
Vue "Android" / Vue simplifiée
Vue "Project" / Vue réel
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView android:text="Hello World!" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/helloWord" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me"
android:id="@+id/button"
android:layout_below="@+id/helloWord"
android:layout_centerHorizontal="true"
android:layout_marginTop="116dp" />
</RelativeLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TextView textView = (TextView) findViewById(R.id.helloWord);
textView.append(" Loaded !");
}
});
}
}
res/layout/activity_main.xml
Code java/MainActivity
Set a breakpoint and launch with debug
Clique "Click me" sur l'application, quand l’exécution passe par le breakpoint. L'exécution s’interrompt, nous avons accès aux fonctions de debuggage
Le code d'Android est en Java
Ce sont :
Chaque dossier peut être spécifique à une taille d'écran :
Ouvrez : en mode 'Project', app/src/main/res
Différent types
Composants d'interface de base
Activity
Une Acitvity est un représentation visuelle d'une application. Une application peut avoir plusieurs activity.
Exemple pour une Twitter:
- A1 : Gestion des tweet (List + détail d'un tweet)
- A2 : Login
- A3 : Configuration du compte
Fragments
Un fragment est un composant qui tourne dans le context d'une activity.
Un fragment encapsule le code fonctionnel pour que ça soit plus simple à réutiliser et pour gérer différentes tailles d'écrans.
User interface widget
Is responsible to arranging other views
Fichiers de ressource Layout
Les interface utilisateur sont défini par les Xml Layout qui se trouve dans res/layout.
Android studio permet d'ajouter des widget en wysiwyg
Relation entre les ressources et le code
Chaque élément dans un layout xml peut être identifié par un id
Ces id peuvent être retrouvés dans le code
Gestion des événements
On peut associer des widget a certain événement
ViewGroup et Layout manager
Un layout Manager est une sous classe de ViewGroup et est responsable de son layout et de celui de ces enfants.
Les plus important layout managers sont :
AbsoluteLayout est deprecated et TableLayout peux être implémenté avec plus d'efficacité par GridLayout
Les layouts permettent aux développeurs de définir des attributs.
Les enfants peuvent aussi définir des attributs qui seront évalué par le layout parents
Par exemple, les enfants peuvent définir leur taille désiré avec les attributs ci-dessous :
FrameLayout
FrameLayout permet de placer les elements enfants au dessus des autres.
Sur ce screenshot, on voit que l'application Gmail utilise le FrameLayout pour faire apparaitre des buttons au dessus du layout courant
LinearLayout
LinearLayout place tous les elements enfants sur un seule colone suivant l'attribut android:orientation
RelativeLayout
android:layout_above | Positions the bottom edge of this view above the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name" |
android:layout_alignBottom | Makes the bottom edge of this view match the bottom edge of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_alignLeft | Makes the left edge of this view match the left edge of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_alignParentBottom | If true, makes the bottom edge of this view match the bottom edge of the parent. Must be a boolean value, either "true" or "false". |
android:layout_alignParentEnd | If true, makes the end edge of this view match the end edge of the parent. Must be a boolean value, either "true" or "false". |
android:layout_alignParentLeft | If true, makes the left edge of this view match the left edge of the parent. Must be a boolean value, either "true" or "false". |
android:layout_alignParentRight | If true, makes the right edge of this view match the right edge of the parent. Must be a boolean value, either "true" or "false". |
android:layout_alignParentStart | If true, makes the start edge of this view match the start edge of the parent. Must be a boolean value, either "true" or "false". |
android:layout_alignParentTop | If true, makes the top edge of this view match the top edge of the parent. Must be a boolean value, either "true" or "false". |
android:layout_alignRight | Makes the right edge of this view match the right edge of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_alignStart | Makes the start edge of this view match the start edge of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_alignTop | Makes the top edge of this view match the top edge of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_below | Positions the top edge of this view below the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_centerHorizontal | If true, centers this child horizontally within its parent. Must be a boolean value, either "true" or "false". |
android:layout_centerInParent | If true, centers this child horizontally and vertically within its parent. Must be a boolean value, either "true" or "false". |
android:layout_centerVertical | If true, centers this child vertically within its parent. Must be a boolean value, either "true" or "false". |
android:layout_toEndOf | Positions the start edge of this view to the end of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_toLeftOf | Positions the right edge of this view to the left of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_toRightOf | Positions the left edge of this view to the right of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
android:layout_toStartOf | Positions the end edge of this view to the start of the given anchor view ID and must be a reference to another resource, in the form "@[+][package:]type:name". |
RelativeLayout
Let's try !
Project name : RelativeLayout
Minimum API : 17
GridLayout
Les gridLayout peuvent être vus comme des tableaux, chaque objet est placé par des numéros de colonne et de ligne
<GridLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:id="@+id/button3"
android:layout_row="0"
android:layout_column="0" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button"
android:id="@+id/button2"
android:layout_row="1"
android:layout_column="1" />
Une application peut être à différent stade suivant l’interaction avec l'utilisateur.
Ces cycles de vie se retrouvent dans le code avec des méthodes, les méthodes les plus importantes sont celle là :
La méthode onDestroy() n'est pas sur de ce faire appeler !
Dans le meilleur des cas, les applications qui sont démarrer par un utilisateur resteraient en mémoire, ce qui les rendras leur redémarrage plus rapide.
Mais dans la réalité, la mémoire disponible pour un téléphone est limité.
Pour gérer ces limites de ressource, Android est permis de tuer l'application pour récupérer des ressources
Si android a besoin de ressource, il va tuer les applications suivant cette ordre :
Project name : LifeCycle
Minimum API : 17
Type : Empty activity
Créer une classe java : TracerActivity.java
public class TracerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
notify("onCreate");
}
@Override
protected void onPause() {
super.onPause();
notify("onPause");
}
@Override
protected void onResume() {
super.onResume();
notify("onResume");
}
@Override
protected void onStop() {
super.onStop();
notify("onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
notify("onDestroy");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
notify("onRestoreInstanceState");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
notify("onSaveInstanceState");
}
private void notify(String methodName) {
String name = this.getClass().getName();
String[] strings = name.split("\\.");
Notification noti = new Notification.Builder(this)
.setContentTitle(methodName + " " + strings[strings.length - 1]).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.setContentText(name).build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify((int) System.currentTimeMillis(), noti);
}
Remplacer la classe MainActivity
public class MainActivity extends TracerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
}
Créer une seconde classe : SecondActivity.java
package com.vogella.android.lifecycle.activity;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class SecondActivity extends TracerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
Sur le layout activity_main.xml, ajouter un bouton et setter l'événement 'Click' sur la fonction 'onClick'
Résolvez les bugs !
Et oui, ca marche rarement du premier coup ;-)
Qu'est ce qu'on observe ?
On continue, dans strings.xml
<string-array name="operating_systems">
<item >Ubuntu</item>
<item >Android</item>
<item >iOS</item>
</string-array>
Dans activity_main.xml, avant le bouton, on ajoute un spinner
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="58dp"
android:entries="@array/operating_systems" />
Qu'est ce qu'on observe ?
Les intents sont des messages asynchrone qui permettent transmettre des messages à d'autres activités.
A l'interieur même d'une application.
Mais aussi avec d'autres applications.
Ce code montre comment vous pouvez démarrer une nouvelle activité avec un intent
# Start the activity connect to the
# specified class
Intent i = new Intent(this, ActivityTwo.class);
startActivity(i);
Il existe deux type d'intents
Implicite
Intent i = new Intent(this, ActivityTwo.class);
i.putExtra("Value1", "This value one for ActivityTwo");
i.putExtra("Value2", "This value two ActivityTwo");
Intent i = new Intent(Intent.ACTION_VIEW
, Uri.parse("http://www.vogella.com"));
startActivity(i);
Explicite
Il est possible de passer par les intents des int, float, String, bundle, Parceable, Serializable
A la fin d'une activity enfant, il est possible de retourner un intent
MainActivity
int code = 42;
startActivityWithResult(indent, code);
Intent i = new Intent();
i.putExtrat("result", "the response !");
setResult(RESULT_OK, i);
Explicite
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Project name : Intents
Minimum API : 17
Type : Empty activity
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<EditText
android:id="@+id/inputforintent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="60dip"
android:text="First Activity"
android:textSize="20sp" >
</EditText>
<Button
android:id="@+id/startintent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/inputforintent"
android:layout_below="@+id/inputforintent"
android:onClick="onClick"
android:text="Calling an intent" />
</RelativeLayout>
activity_result.xml
<?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/displayintentextra"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Input"
/>
<EditText
android:id="@+id/returnValue"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<requestFocus />
</EditText>
</LinearLayout>
Crée une class ResultActivity
<activity
android:label="Result Activity"
android:name=".ResultActivity" >
</activity>
On oublie pas d'ajouter la class au manifest
public class ResultActivity extends Activity {
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_result);
}
}
Dans MainActivity.java, on remplit les todos
public void onClick(View view) {
EditText text = (EditText) findViewById(R.id.inputforintent);
// used later
String value = text.getText().toString();
// TODO 1 create new Intent(context, class)
// use the activity as context parameter
// and "ResultActivity.class" for the class parameter
// TODO 2 start second activity with
// startActivity(intent);
}
Dans ResultActivity, on va récupérer les données stocké dans l'intent
getIntent().getExtras().getString("MY_STRING")
Et on le passe au EditText
On compile et on vérifie que tout fonctionne
Dans ResultActivity
@Override
public void finish() {
// TODO 1 create new Intent
// Intent intent = new Intent();
// TODO 2 read the data of the EditText field
// with the id returnValue
// TODO 3 put the text from EditText
// as String extra into the intent
// use editText.getText().toString();
// TODO 4 use setResult(RESULT_OK, intent);
// to return the Intent to the application
super.finish();
}
Dans MainActivity, dans la méthode onClick(View view), remplace
startActivity(i);
par
startActivity(i, REQUEST_CODE);
Au début de la class, n'oublie pas de déclarer REQUEST_CODE en static
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
if (data.hasExtra("returnkey")) {
String result = data.getExtras().getString("returnkey");
if (result != null && result.length() > 0) {
Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
}
}
}
}
Résout les derniers trous et bugs
L'affichage de liste ou de grille est trés courant dans les applications mobiles.
L'utilisateur voit une collection d'object et peut scroller dessus.
Typiquement quand un utilisateur clique sur un élément de cette liste, l'application bascule sur une autre activité.
Les adapter sont des models de donnée, chaqu'un de ses objects représente un item dans la liste
Un adapter pour un RecyclerView est une extension de RecyclerView.Adapter.
Et peut avoir plusieurs sortes de donnée, tel qu'un titre, une description, une image, ...
Un object d'un recycler est inflate avec son propre layout.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Description"
android:textSize="12sp" />
<TextView
android:id="@+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="Example application"
android:textSize="16sp" />
</RelativeLayout>
Il faut ajouter la dépendance suivant dans gradle
dependencies {
...
compile "com.android.support:recyclerview-v7:23.0.1"
}
Il est possible d'avoir des layout différent pour chaque item :
Une animation customiser avec une classe hérité de Recycler.ItemAnimator
L'ordre et le tri doit être fait sur l'adapter. Le RecyclerView affiche uniquement les données
Project name : RecyclerView
Minimum API : 17
Type : Empty activity
dependencies {
...
compile "com.android.support:recyclerview-v7:23.0.1"
}
Ajouter la ligne de compile dans build.gradle (Module: app)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="12dp"
android:layout_marginRight="12dp"
android:elevation="2dp"
android:src="@drawable/ic_add_circle" />
</RelativeLayout>
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Description"
android:textSize="12sp" />
<TextView
android:id="@+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="Example application"
android:textSize="16sp" />
</RelativeLayout>
items.xml
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<String> mDataset;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView txtHeader;
public TextView txtFooter;
public ViewHolder(View v) {
super(v);
txtHeader = (TextView) v.findViewById(R.id.firstLine);
txtFooter = (TextView) v.findViewById(R.id.secondLine);
}
}
public void add(int position, String item) {
mDataset.add(position, item);
notifyItemInserted(position);
}
public void remove(String item) {
int position = mDataset.indexOf(item);
mDataset.remove(position);
notifyItemRemoved(position);
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<String> myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
final String name = mDataset.get(position);
holder.txtHeader.setText(mDataset.get(position));
holder.txtHeader.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
remove(name);
}
});
holder.txtFooter.setText("Footer: " + mDataset.get(position));
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.size();
}
}
Créer la classe MyAdapter
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<String> myDataset = new ArrayList<String>();
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
mAdapter.add(0, "Salut");
mAdapter.add(1, "ca");
mAdapter.add(2, "va");
}
Dans MainActivity
On compile !
On commente le code
On continue :
Rendre le bouton + clickable et crée un événement pour qu'il ajoute un item à chaque clique
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<String> myDataset = new ArrayList<String>();
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
ArrayList<String> items = new ArrayList<String>();
items.add("Premier message");
items.add("Second message");
items.add("Troixième message");
items.add("Quatrième message");
//code here
// mAdapter.add(???)
}
On continue :
Ajouter la classe Message
public class Message {
public String contact = "";
public String message = "";
public Message(String contact, String message) {
this.message = message;
this.contact = contact;
}
public String getContact() {
return contact;
}
public void setContact(String contact) {
this.contact = contact;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Au lieu que l'adapter soit associé à un ArrayList<String>, on l'associe avec un ArrayList<Message>
- Changer le code pour gérer cet nouvel ArrayList
- Dans onBindViewHolder, associé le contact avec txtHeader et message avec TxtFooter.
ArrayList<Message> items = new ArrayList<Message>();
items.add(new Message("0642698745", "Premier message"));
items.add(new Message("0654879635", "Second message"));
items.add(new Message("0654323698", "Troixième message"));
items.add(new Message("0610584559", "Quatrième message"));
Project name : ChatWithMe
Minimum API : 21
Type : Empty activity
Constuire les designs suivant
activity_main
activity_contacts_list
activity_messages
Constuire les designs suivant
item_contact
item_message
Hint, appliquer la hauteur :
android:layout_height="?android:attr/listPreferredItemHeight"
Constuire les designs suivant
item_contact
item_message
Hint, appliquer la hauteur :
android:layout_height="?android:attr/listPreferredItemHeight"
1 - Lier toutes les activités entre-elles
2 - Developper la gestion des recyclerView et des adapters.
Ajouter des fausses données
Il est possible de lancer des intents pour d'autres applications.
String url = "http://www.google.com";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
Un composant d'une application peut s'enregistrer avec un intent filter pour recevoir des intents.
Un intent filter permet de spécifier un type d'intent qu'une activity, un service ou un broadcaster peut recevoir
S'inscrire pour ouvrir une page web
<activity android:name=".BrowserActivitiy"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http"/>
</intent-filter>
</activity>
S'inscrire pour recevoir du Content Type text/plain
<activity
android:name=".ActivityTest"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Project name : MyBrowser
Minimum API : 17
Type : Empty activity
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Launch CallBrowser"
android:id="@+id/textView"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".BrowserActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webView2"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="false"
/>
</RelativeLayout>
activity_browser.xml
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_browser);
// To keep this example simple, we allow network access
// in the user interface thread
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
.permitAll().build();
StrictMode.setThreadPolicy(policy);
Intent intent = getIntent();
WebView webView = (WebView) findViewById(R.id.webView2);
// To get the action of the intent use
String action = intent.getAction();
if (!action.equals(Intent.ACTION_VIEW)) {
throw new RuntimeException("Should not happen");
}
// To get the data use
Uri data = intent.getData();
URL url;
try {
url = new URL(data.getScheme(), data.getHost(), data.getPath());
webView.setWebViewClient(new WebViewClient());
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(url.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
Crée une class java, BrowserActivity
<uses-permission android:name="android.permission.INTERNET" ></uses-permission>
<uses-permission android:name="android.permission.BLUETOOTH" ></uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".BrowserActivity" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
</activity>
</application>
AndroidManifest.xml
On commente le code
Pour tester l'application, il nous faut une application qui appelle une url
Project name : CallBrowser
Minimum API : 17
Type : Empty activity
Les services sont des composants qui tournent en background sans aucune intéraction avec l'utilisateur.
Il n'a aucune interface utilisateur.
Les services sont utilisés pour les actions répétitives et/ou longues.
Les services tournent avec un plus grosse priorités que les composants ordinaires.
De la même manière que les applications, il n'est pas possible de faire tourner des actions bloquant sur le main thread.
AndroidManifest.xml
Start service
<service
android:name="MyService"
android:icon="@drawable/icon"
android:label="@string/service_name"
>
</service>
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//TODO do something useful
return Service.START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
//TODO for communication return IBinder implementation
return null;
}
}
// use this to start and trigger a service
Intent i= new Intent(context, MyService.class);
// potentially add data to the intent
i.putExtra("KEY1", "Value to be used by the service");
context.startService(i);
On peut aussi démarrer le service bindService(), cela nous permettra de communiquer directement avec le service.
Implémentation et déclaration
Démarrage
Aprés que startService(intent) soit appelé, le service ne tourne toujours pas. Le service est créé et la méthode onCreate() est appelé.
Une fois que le service est démarré, la méthode onStartCommand(Intent) est appelée. On peut récupérer l'Intent.
Même si vous appelez plusieurs fois startService(), le service ne sera appelé qu'une seule fois.
Redémarrage
La méthode appelé doit retourne la méthode de redémarrage du service
options | Description |
---|---|
Service.START_STICKY | Le service est redémarré. L'intent ne sera pas renvoyé. |
Service.START_NOT_STICKY | Le service ne sera pas redémarré. |
Service.START_REDELIVER_INTENT | Le service est redémarré. L'intent sera renvoyé. |
Arret
Pour arrêter le service :
Depuis l'application, on faut appeler stopService()
Depuis le service lui-même, il faut appeler stopSelf()
IntentServices
Il est possible de lancer les services pour une seule fois.
Une fois que fois que le service est fini, onHandleIntent() est appelé dans l'application.
IntentService ou AsyncTask ?
AsyncTask est une suite de gestions de thread avec beaucoup d'options. Très pratique à mettre en place. Est tout à fait adapté pour les petites tâches récurrentes.
IntentService est similaire à AsyncTask, mais il permet de faire continuer l'exécution même quand l'application est en background.
Par exemple, une application qui upload une vidéo, avec l'application, on sélectionne la vidéo et l'utilisateur n'est pas obligé de laisser l'application ouverte pour que l'upload fonctionne.
BindService
Si une activité veut interagir avec avec un service, elle peut appeler la méthode bindService().
Local services bindings
Si le service est lancé à l'interrieur d'une activité, il est possible de retourne le service dans l'activité. Ca permet d'accéder aux methodes du service directement
Communication interprocesses
Si le service tourne dans son propre service, on a besoin de l'IPC (interprocess Communication) pour communiquer avec le service.
Lancer un service dans son propre processus
Les deux points à l'élément process indique à android que ce service est privé à son application. Sinon, le service pourrait être accédé par d'autres application.
Et si vous voulez communiqué avec le service à travers le réseau, il faudra faire les appels en asynchrone. Android n'autorise pas de faire ces appels sur le main thread.
<service
android:name="WordService"
android:process=":my_process"
android:icon="@drawable/icon"
android:label="@string/service_name"
>
</service>
Communiquer avec un service ?
Soit par les intents, c'est le scénario le plus simple.
Soit en utilisant les receivers
Si le service est local, on peut accéder aux méthodes de la classe
Soit en utilisant les IPC par les fichiers Aidl
Soit par le réseau
Project name : IntentService
Minimum API : 17
Type : Empty activity
Exercice 1 : Les intentServices
Class DownloadService
public class DownloadService extends IntentService {
private int result = Activity.RESULT_CANCELED;
public static final String URL = "urlpath";
public static final String FILENAME = "filename";
public static final String FILEPATH = "filepath";
public static final String RESULT = "result";
public static final String NOTIFICATION = "com.meuuh.android.service.receiver";
public DownloadService() {
super("DownloadService");
}
// will be called asynchronously by Android
@Override
protected void onHandleIntent(Intent intent) {
String urlPath = intent.getStringExtra(URL);
String fileName = intent.getStringExtra(FILENAME);
File output = new File(Environment.getExternalStorageDirectory(),
fileName);
if (output.exists()) {
output.delete();
}
InputStream stream = null;
FileOutputStream fos = null;
try {
URL url = new URL(urlPath);
stream = url.openConnection().getInputStream();
InputStreamReader reader = new InputStreamReader(stream);
fos = new FileOutputStream(output.getPath());
int next = -1;
while ((next = reader.read()) != -1) {
fos.write(next);
}
// successfully finished
result = Activity.RESULT_OK;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
publishResults(output.getAbsolutePath(), result);
}
private void publishResults(String outputPath, int result) {
Intent intent = new Intent(NOTIFICATION);
intent.putExtra(FILEPATH, outputPath);
intent.putExtra(RESULT, result);
sendBroadcast(intent);
}
}
Class MainActivity
public class MainActivity extends AppCompatActivity {
private TextView textView;
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
String string = bundle.getString(DownloadService.FILEPATH);
int resultCode = bundle.getInt(DownloadService.RESULT);
if (resultCode == RESULT_OK) {
Toast.makeText(MainActivity.this,
"Download complete. Download URI: " + string,
Toast.LENGTH_LONG).show();
textView.setText("Download done");
} else {
Toast.makeText(MainActivity.this, "Download failed",
Toast.LENGTH_LONG).show();
textView.setText("Download failed");
}
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.status);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(receiver, new IntentFilter(DownloadService.NOTIFICATION));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
public void onClick(View view) {
Intent intent = new Intent(this, DownloadService.class);
// add infos for the service which file to download and where to store
intent.putExtra(DownloadService.FILENAME, "image.jpg");
intent.putExtra(DownloadService.URL,
"http://s2.quickmeme.com/img/d2/d2a55033bd85f86fdb077fc6f1fb54db0d4543d2c1951a6b174a15d650c8ad90.jpg");
startService(intent);
textView.setText("Service started");
}
}
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.meuuh.intentservice" >
<uses-sdk
android:minSdkVersion="17"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".DownloadService" >
</service>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Download"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="63dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status: "
android:textSize="@dimen/abc_text_size_large_material" />
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not started"
android:textSize="@dimen/abc_text_size_large_material" />
</LinearLayout>
</RelativeLayout>
Project name : OwnService
Minimum API : 17
Type : Empty activity
Exercice 1 : Les intentServices
Class DownloadService
public class DownloadService extends IntentService {
private int result = Activity.RESULT_CANCELED;
public static final String URL = "urlpath";
public static final String FILENAME = "filename";
public static final String FILEPATH = "filepath";
public static final String RESULT = "result";
public static final String NOTIFICATION = "com.meuuh.android.service.receiver";
public DownloadService() {
super("DownloadService");
}
// will be called asynchronously by Android
@Override
protected void onHandleIntent(Intent intent) {
String urlPath = intent.getStringExtra(URL);
String fileName = intent.getStringExtra(FILENAME);
File output = new File(Environment.getExternalStorageDirectory(),
fileName);
if (output.exists()) {
output.delete();
}
InputStream stream = null;
FileOutputStream fos = null;
try {
URL url = new URL(urlPath);
stream = url.openConnection().getInputStream();
InputStreamReader reader = new InputStreamReader(stream);
fos = new FileOutputStream(output.getPath());
int next = -1;
while ((next = reader.read()) != -1) {
fos.write(next);
}
// successfully finished
result = Activity.RESULT_OK;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
publishResults(output.getAbsolutePath(), result);
}
private void publishResults(String outputPath, int result) {
Intent intent = new Intent(NOTIFICATION);
intent.putExtra(FILEPATH, outputPath);
intent.putExtra(RESULT, result);
sendBroadcast(intent);
}
}
Class MainActivity
public class MainActivity extends AppCompatActivity {
private TextView textView;
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
String string = bundle.getString(DownloadService.FILEPATH);
int resultCode = bundle.getInt(DownloadService.RESULT);
if (resultCode == RESULT_OK) {
Toast.makeText(MainActivity.this,
"Download complete. Download URI: " + string,
Toast.LENGTH_LONG).show();
textView.setText("Download done");
} else {
Toast.makeText(MainActivity.this, "Download failed",
Toast.LENGTH_LONG).show();
textView.setText("Download failed");
}
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.status);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(receiver, new IntentFilter(DownloadService.NOTIFICATION));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
public void onClick(View view) {
Intent intent = new Intent(this, DownloadService.class);
// add infos for the service which file to download and where to store
intent.putExtra(DownloadService.FILENAME, "image.jpg");
intent.putExtra(DownloadService.URL,
"http://s2.quickmeme.com/img/d2/d2a55033bd85f86fdb077fc6f1fb54db0d4543d2c1951a6b174a15d650c8ad90.jpg");
startService(intent);
textView.setText("Service started");
}
}
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.meuuh.intentservice" >
<uses-sdk
android:minSdkVersion="17"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".DownloadService" >
</service>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Download"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="63dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status: "
android:textSize="@dimen/abc_text_size_large_material" />
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not started"
android:textSize="@dimen/abc_text_size_large_material" />
</LinearLayout>
</RelativeLayout>
Les broadcast receivers ou plus simplement nommé les receivers, sont des composants androidqui permet de s'inscrire à des événements systèmes.
Par exemple, on peut s'inscrire à l'événement ACTION_BOOT_COMPLETED, qui sera appelé dès que le système android aura completement fini sont process de boot.
Pour s'enregistrer, on peut renseigner l'android_manifest
Ou plus dynamiquement grâce à Context.registerReceiver()
Il faut que la classe recevant le receiver soit étendu de BroadcastReceiver
Quand l'event est appelé, la méthode onReceiver() est appelé.
Après que la méthode onReceiver() soit appelé, Android va recycler le receiver.
Depuis API 11, vous pouvez appeler goAsync() qui retourne un object PendingResult. Android va considérer que le receiver est toujours actif jusqu'a ce que PendingResult.finish() soit appelé.
/!\ A partir de Android 3, Android exclut tous les receivers par défaut avant que l'utilisateur ne le lance ou si l'utilisateur stop volontairement l'application dans la configuration.
Il faut uniquement lancer une fois l'application pour enlever cette restriction. Au prochain reboot la restriction sera toujours retiré à moins que l'utilisateur ne stop l'application.
Certains événements sont définis comme des champs final static
D'autres sont dans des classes d'Android, par exemple TelephonyManager.
Pour démarrer automatiquement un Service à partir un Receiver
<application
[...]
<receiver android:name=".MyReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// assumes WordService is a registered service
Intent intent = new Intent(context, WordService.class);
context.startService(intent);
}
}
Implémenter un receiver pour un événement lié au téléphone
<uses-permission android:name="android.permission.READ_PHONE_STATE" ></uses-permission>
<application
[...]
<receiver android:name="MyPhoneReceiver" >
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" >
</action>
</intent-filter>
</receiver>
</application>
public class MyPhoneReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
String state = extras.getString(TelephonyManager.EXTRA_STATE);
Log.w("MY_DEBUG_TAG", state);
if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
String phoneNumber = extras
.getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
Log.w("MY_DEBUG_TAG", phoneNumber);
}
}
}
}
Project name : AlarmReceiver
Minimum API : 17
Type : Empty activity
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.meuuh.alarmreceiver" >
<uses-permission android:name="android.permission.VIBRATE" >
</uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".MyBroadcastReceiver" >
</receiver>
</application>
</manifest>
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Time's up buddy !",
Toast.LENGTH_LONG).show();
// Vibrate the mobile phone
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(2000);
}
}
public void startAlert(View view) {
EditText text = (EditText) findViewById(R.id.time);
int i = Integer.parseInt(text.getText().toString());
Intent intent = new Intent(this, MyBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), 234324243, intent, 0);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ (i * 1000), pendingIntent);
Toast.makeText(this, "Alarm set in " + i + " seconds",
Toast.LENGTH_LONG).show();
}
On push, on test, on commente
<receiver android:name="MyReceiver" >
<intent-filter>
<action android:name="com.meuuh.android.mybroadcast" />
</intent-filter>
</receiver>
Intent intent = new Intent();
intent.setAction("com.android.android.mybroadcast");
sendBroadcast(intent);
Il est possible d'enregistrer nos propres événements
Ajouter un nouveau bouton au layout.
Quand on clique sur ce bouton, vous appellerez l'action que vous aurez défini dans le manifest
Android modifie l'UI et gères les input event avec un seul thread. On l'appel le main thread
Android collecte tous les events dans une queue et process ces tasks dans le looper.
Dans le main thread, chaque tache est résolu l'un aprés l'autre.
Si nous gérons mal le code, et que nous mettons un longue tâche dans le main thread, l'UI va se bloquer.
Par example: HTTP request, SQL Request, ...
Pour assurer une bonne expérience utilisateur, il faut lancer ces tables dans des taches asynchrones
De toutes les facons, si nous bloquons l'application pendant plus de 5 secondes, Android la tue.
"Application not responding"
Une solution est d'utiliser les Threads de Java.
Android met à disposition les classes ci-dessous :
Thread
java.utils.concurrent
ThreadPools
Executor
Mais il faut gérer soit même :
Pour toutes ces raisons, Android a développer ses propres méthodes.
Android nous met à disposition 3 classes ci-dessous :
Project name : DownloadHtml
Minimum API : 17
Type : Empty activity
Construire activity_main.xml
MainActivity
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.TextView01);
}
private class DownloadWebPageTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
String response = "";
for (String url : urls) {
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
response += ("Get response for url : ") + response.concat(url);
}
return response;
}
@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}
public void onClick(View view) {
DownloadWebPageTask task = new DownloadWebPageTask();
task.execute(new String[]{"https://www.google.com"});
}
}
On commente le code
Seconde partie
Cacher par default le progressBar
Quand on clique sur le bouton, on fait apparaitre la progressBar
Quand la tache est fini, on cache le progressBar
Volley
Volley
Volley est une library de Google, présenté lors du Google I/O de 2013
Avant Volley, pour récupérer un object sur internet (JSON, HTML, images, ...), on pouvait utilisé deux classes.
java.net.HttpUrlConnection
org.apache.http.client
Et ces deux libs n'avait comme fonction d'uniquement faire les requêtes HTTP. Pas de cache, pas de priorité.
Il fallait TOUT développer soit même ....
Volley
Pourquoi Volley ?
- Eviter les libs HttpClient et HttpUrlConnection.
Elles sont deprecated API 22 et supprimé API 23
- Eviter AsyncTask
Depuis l'API 11, on doit faire les requêtes réseaux dans un thread. On doit implémenter la couche l'AsyncTask ...
De plus les requêtes avec AsyncTask sont FIFO. L'application doit finir de télécharger toute la page pour commencer à télécharger le reste du contenu.
Par exemple facebook, tweeter
Volley
... Pourquoi Volley ?
Volley
On ajoute la library grâce à Maven, build.gradle (Module: app)
compile 'com.mcxiaoke.volley:library-aar:1.0.0'
On n'oublie pas les permissions
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Request GET
String url = "http://httpbin.org/html";
// Request a string response
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Result handling
System.out.println(response.substring(0,100));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Error handling
System.out.println("Something went wrong!");
error.printStackTrace();
}
});
// Add the request to the queue
Volley.newRequestQueue(this).add(stringRequest);
Volley
Request JSON
String url = "http://httpbin.org/get?site=code&network=tutsplus";
JsonObjectRequest jsonRequest = new JsonObjectRequest
(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// the response is already constructed as a JSONObject!
try {
response = response.getJSONObject("args");
String site = response.getString("site"),
network = response.getString("network");
System.out.println("Site: "+site+"\nNetwork: "+network);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
});
Volley.newRequestQueue(this).add(jsonRequest);
Request Image
String url = "http://i.imgur.com/Nwk25LA.jpg";
mImageView = (ImageView) findViewById(R.id.image);
ImageRequest imgRequest = new ImageRequest(url,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
mImageView.setImageBitmap(response);
}
}, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.ARGB_8888, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mImageView.setBackgroundColor(Color.parseColor("#ff0000"));
error.printStackTrace();
}
});
Volley.newRequestQueue(this).add(imgRequest);
Volley
Request POST
String url = "http://httpbin.org/post";
StringRequest postRequest = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
try {
JSONObject jsonResponse = new JSONObject(response).getJSONObject("form");
String site = jsonResponse.getString("site"),
network = jsonResponse.getString("network");
System.out.println("Site: "+site+"\nNetwork: "+network);
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
}
) {
@Override
protected Map<String, String> getParams()
{
Map<String, String> params = new HashMap<>();
// the POST parameters:
params.put("site", "code");
params.put("network", "tutsplus");
return params;
}
};
Volley.newRequestQueue(this).add(postRequest);
Project name : GetJson
Minimum API : 17
Type : Empty activity
Créer un projet qui téléchargera du JSON depuis
https://api.summview.com/v2/debug
et l'affichera dans un textView
Bonus : Parser ce JSON
http://www.tutorialspoint.com/android/android_json_parser.htm
Picasso
Picasso
On ajoute la library grâce à Maven, build.gradle (Module: app)
compile 'com.squareup.picasso:picasso:2.3.3'
On n'oublie pas les permissions
<uses-permission android:name="android.permission.INTERNET" />
Request GET
String url = "http://httpbin.org/html";
// Request a string response
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Result handling
System.out.println(response.substring(0,100));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Error handling
System.out.println("Something went wrong!");
error.printStackTrace();
}
});
// Add the request to the queue
Volley.newRequestQueue(this).add(stringRequest);
Project name : DownloadImages
Minimum API : 17
Type : Empty activity
Crée un projet avec :
- Une ImageView et un bouton
- Quand on clique sur le bouton, un fois sur 3, il exécutera ces 3 morceaux de code dans l'ordre.
Picasso.with(self)
.load("http://s2.quickmeme.com/img/d2/d2a55033bd85f86fdb077fc6f1fb54db0d4543d2c1951a6b174a15d650c8ad90.jpg?t="+options)
.into(imageView);
Picasso.with(self)
.load("http://s2.quickmeme.com/img/d2/d2a55033bd85f86fdb077fc6f1fb54db0d4543d2c1951a6b174a15d650c8ad90.jpg?t="+options)
.rotate(180)
.into(imageView);
Picasso.with(self)
.load("http://s2.quickmeme.com/img/d2/d2a55033bd85f86fdb077fc6f1fb54db0d4543d2c1951a6b174a15d650c8ad90.jpg?t="+options)
.placeholder(R.drawable.loading)
.into(imageView);
Méthodes de stockage local :
Méthodes de stockage local :
Android propose d'avoir un stockage interne et externe. Le stockage externe n'est pas obligatoire. Certain device n'en ont pas.
Android propose de déplacer le stockage de l'application de l'interne vers l'externe. Il déplacera tout sauf les préferences et les databases
Internal vs external
Les shared preferences sont des pairs de key-value contenant des primitives.
Les définitions peuvent se faire grâce à une ressource XML.
Shared preferences
Récupération
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity();
String url = settings.getString("url", "n/a");
Modification
Editor edit = preferences.edit();
edit.putString("username", "new_value_for_user");
edit.apply();
Shared preferences
Il est possible d'écouter les changements grâce à un listener
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(this);
// Instance field for listener
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// Your Implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
Développer la fonctionnalité suivante :
Une fois que l'utilisateur a rentré son numéro de téléphone, au futur redémarrage, la page de login ne lui est plus présenté.
Files
Pour accéder au file internal ou external, il faut utiliser les libraries Java de base.
Pour accéder au fichier sur l'external FS, il faut ajouter des permissions supplémentaires
android.permission.READ_EXTERNAL_STORAGE
android.permission.WRITE_EXTERNAL_STORAGE
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
Pour récupérer le répertoire de l'external storage
private void readFileFromSDCard() {
File directory = Environment.getExternalStorageDirectory();
// assumes that a file article.rss is available on the SD card
File file = new File(directory + "/article.rss");
if (!file.exists()) {
throw new RuntimeException("File not found");
}
Log.e("Testing", "Starting to read");
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Example de code
Android utilise le moteur SQLite
SQLite est une base de donnée openSource qui respecte les standards de la syntaxe SQL.
La base de donnée n'a besoin d'uniquement 250ko de mémoire pour fonctionner.
La base de données supportent 3 types de données :
SQLite est embarqué dans tous les devices android. Utiliser une base de donnée Android ne demande pas de procédure particulière d'installation.
Accéder à la base donnée implique d'accéder au système de fichier. Ca peut être lent. C'est recommandé de me mettre la base de donnée en asynchrone.
Pour créer ou mettre à jour une DB, il faut créer une sous class de SQLiteOpenHelper.
Dans la méthode super(), spécifiez le nom de la DB et la version courante.
Dans la sous classe, on a besoin d'override les méthodes suivante :
onCreate() : Quand la DB est accédée mais pas encore crée.
onUpgrade() : Quand la version a augmentée et qu'il faut mettre à jour la DB.
Ces deux méthodes retournent un object SQLiteDatabase qui nous propose deux méthodes getReadableDatabase() et getWritableDatabase().
(Etude de code)
Project name : Todos
Minimum API : 17
Type : Empty activity
(Etude de code)
Class TodoDetailHelper
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by Doud on 10/9/2015.
*/
public class TodoDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "todotable.db";
private static final int DATABASE_VERSION = 1;
public TodoDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Method is called during creation of the database
@Override
public void onCreate(SQLiteDatabase database) {
TodoTable.onCreate(database);
}
// Method is called during an upgrade of the database,
// e.g. if you increase the database version
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion,
int newVersion) {
TodoTable.onUpgrade(database, oldVersion, newVersion);
}
}
Class TodoDetailActivity
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import com.meuuh.mytodo.contentprovider.MyTodoContentProvider;
/*
* TodoDetailActivity allows to enter a new todo item
* or to change an existing
*/
public class TodoDetailActivity extends Activity {
private Spinner mCategory;
private EditText mTitleText;
private EditText mBodyText;
private Uri todoUri;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.todo_edit);
mCategory = (Spinner) findViewById(R.id.category);
mTitleText = (EditText) findViewById(R.id.todo_edit_summary);
mBodyText = (EditText) findViewById(R.id.todo_edit_description);
Button confirmButton = (Button) findViewById(R.id.todo_edit_button);
Bundle extras = getIntent().getExtras();
// check from the saved Instance
todoUri = (bundle == null) ? null : (Uri) bundle
.getParcelable(MyTodoContentProvider.CONTENT_ITEM_TYPE);
// Or passed from the other activity
if (extras != null) {
todoUri = extras
.getParcelable(MyTodoContentProvider.CONTENT_ITEM_TYPE);
fillData(todoUri);
}
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (TextUtils.isEmpty(mTitleText.getText().toString())) {
makeToast();
} else {
setResult(RESULT_OK);
finish();
}
}
});
}
private void fillData(Uri uri) {
String[] projection = { TodoTable.COLUMN_SUMMARY,
TodoTable.COLUMN_DESCRIPTION, TodoTable.COLUMN_CATEGORY };
Cursor cursor = getContentResolver().query(uri, projection, null, null,
null);
if (cursor != null) {
cursor.moveToFirst();
String category = cursor.getString(cursor
.getColumnIndexOrThrow(TodoTable.COLUMN_CATEGORY));
for (int i = 0; i < mCategory.getCount(); i++) {
String s = (String) mCategory.getItemAtPosition(i);
if (s.equalsIgnoreCase(category)) {
mCategory.setSelection(i);
}
}
mTitleText.setText(cursor.getString(cursor
.getColumnIndexOrThrow(TodoTable.COLUMN_SUMMARY)));
mBodyText.setText(cursor.getString(cursor
.getColumnIndexOrThrow(TodoTable.COLUMN_DESCRIPTION)));
// always close the cursor
cursor.close();
}
}
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveState();
outState.putParcelable(MyTodoContentProvider.CONTENT_ITEM_TYPE, todoUri);
}
@Override
protected void onPause() {
super.onPause();
saveState();
}
private void saveState() {
String category = (String) mCategory.getSelectedItem();
String summary = mTitleText.getText().toString();
String description = mBodyText.getText().toString();
// only save if either summary or description
// is available
if (description.length() == 0 && summary.length() == 0) {
return;
}
ContentValues values = new ContentValues();
values.put(TodoTable.COLUMN_CATEGORY, category);
values.put(TodoTable.COLUMN_SUMMARY, summary);
values.put(TodoTable.COLUMN_DESCRIPTION, description);
if (todoUri == null) {
// New todo
todoUri = getContentResolver().insert(MyTodoContentProvider.CONTENT_URI, values);
} else {
// Update todo
getContentResolver().update(todoUri, values, null, null);
}
}
private void makeToast() {
Toast.makeText(TodoDetailActivity.this, "Please maintain a summary",
Toast.LENGTH_LONG).show();
}
}
Class TodosOverviewActivity
import android.app.ListActivity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import com.meuuh.mytodo.contentprovider.MyTodoContentProvider;
/*
* TodosOverviewActivity displays the existing todo items
* in a list
*
* You can create new ones via the ActionBar entry "Insert"
* You can delete existing ones via a long press on the item
*/
public class TodosOverviewActivity extends ListActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final int ACTIVITY_CREATE = 0;
private static final int ACTIVITY_EDIT = 1;
private static final int DELETE_ID = Menu.FIRST + 1;
// private Cursor cursor;
private SimpleCursorAdapter adapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.todo_list);
this.getListView().setDividerHeight(2);
fillData();
registerForContextMenu(getListView());
}
// create the menu based on the XML defintion
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.listmenu, menu);
return true;
}
// Reaction to the menu selection
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.insert:
createTodo();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case DELETE_ID:
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
.getMenuInfo();
Uri uri = Uri.parse(MyTodoContentProvider.CONTENT_URI + "/"
+ info.id);
getContentResolver().delete(uri, null, null);
fillData();
return true;
}
return super.onContextItemSelected(item);
}
private void createTodo() {
Intent i = new Intent(this, TodoDetailActivity.class);
startActivity(i);
}
// Opens the second activity if an entry is clicked
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, TodoDetailActivity.class);
Uri todoUri = Uri.parse(MyTodoContentProvider.CONTENT_URI + "/" + id);
i.putExtra(MyTodoContentProvider.CONTENT_ITEM_TYPE, todoUri);
startActivity(i);
}
private void fillData() {
// Fields from the database (projection)
// Must include the _id column for the adapter to work
String[] from = new String[] { TodoTable.COLUMN_SUMMARY };
// Fields on the UI to which we map
int[] to = new int[] { R.id.label };
getLoaderManager().initLoader(0, null, this);
adapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, from,
to, 0);
setListAdapter(adapter);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
}
// creates a new loader after the initLoader () call
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = { TodoTable.COLUMN_ID, TodoTable.COLUMN_SUMMARY };
CursorLoader cursorLoader = new CursorLoader(this,
MyTodoContentProvider.CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// data is not available anymore, delete reference
adapter.swapCursor(null);
}
}
TodoTable
public class TodoTable {
// Database table
public static final String TABLE_TODO = "todo";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_CATEGORY = "category";
public static final String COLUMN_SUMMARY = "summary";
public static final String COLUMN_DESCRIPTION = "description";
// Database creation SQL statement
private static final String DATABASE_CREATE = "create table "
+ TABLE_TODO
+ "("
+ COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_CATEGORY + " text not null, "
+ COLUMN_SUMMARY + " text not null,"
+ COLUMN_DESCRIPTION
+ " text not null"
+ ");";
public static void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
public static void onUpgrade(SQLiteDatabase database, int oldVersion,
int newVersion) {
Log.w(TodoTable.class.getName(), "Upgrading database from version "
+ oldVersion + " to " + newVersion
+ ", which will destroy all old data");
database.execSQL("DROP TABLE IF EXISTS " + TABLE_TODO);
onCreate(database);
}
}
(Etude de code)
Class contentprovider/MyTodoContentProvider
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import com.meuuh.mytodo.TodoDatabaseHelper;
import com.meuuh.mytodo.TodoTable;
import java.util.Arrays;
import java.util.HashSet;
/**
* Created by Doud on 10/9/2015.
*/
public class MyTodoContentProvider extends ContentProvider {
// database
private TodoDatabaseHelper database;
// used for the UriMacher
private static final int TODOS = 10;
private static final int TODO_ID = 20;
private static final String AUTHORITY = "com.meuuh.android.todos.contentprovider";
private static final String BASE_PATH = "todos";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
+ "/todos";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
+ "/todo";
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, BASE_PATH, TODOS);
sURIMatcher.addURI(AUTHORITY, BASE_PATH + "/#", TODO_ID);
}
@Override
public boolean onCreate() {
database = new TodoDatabaseHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Uisng SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// check if the caller has requested a column which does not exists
checkColumns(projection);
// Set the table
queryBuilder.setTables(TodoTable.TABLE_TODO);
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case TODOS:
break;
case TODO_ID:
// adding the ID to the original query
queryBuilder.appendWhere(TodoTable.COLUMN_ID + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = database.getWritableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
// make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
long id = 0;
switch (uriType) {
case TODOS:
id = sqlDB.insert(TodoTable.TABLE_TODO, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
switch (uriType) {
case TODOS:
rowsDeleted = sqlDB.delete(TodoTable.TABLE_TODO, selection,
selectionArgs);
break;
case TODO_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete(TodoTable.TABLE_TODO,
TodoTable.COLUMN_ID + "=" + id,
null);
} else {
rowsDeleted = sqlDB.delete(TodoTable.TABLE_TODO,
TodoTable.COLUMN_ID + "=" + id
+ " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case TODOS:
rowsUpdated = sqlDB.update(TodoTable.TABLE_TODO,
values,
selection,
selectionArgs);
break;
case TODO_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(TodoTable.TABLE_TODO,
values,
TodoTable.COLUMN_ID + "=" + id,
null);
} else {
rowsUpdated = sqlDB.update(TodoTable.TABLE_TODO,
values,
TodoTable.COLUMN_ID + "=" + id
+ " and "
+ selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
private void checkColumns(String[] projection) {
String[] available = { TodoTable.COLUMN_CATEGORY,
TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_DESCRIPTION,
TodoTable.COLUMN_ID };
if (projection != null) {
HashSet<String> requestedColumns = new HashSet<String>(Arrays.asList(projection));
HashSet<String> availableColumns = new HashSet<String>(Arrays.asList(available));
// check if all columns which are requested are available
if (!availableColumns.containsAll(requestedColumns)) {
throw new IllegalArgumentException("Unknown columns in projection");
}
}
}
}
layout/todo_edit.xml
<?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" >
<Spinner
android:id="@+id/category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:entries="@array/priorities" >
</Spinner>
<LinearLayout
android:id="@+id/LinearLayout01"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<EditText
android:id="@+id/todo_edit_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/todo_edit_summary"
android:imeOptions="actionNext" >
</EditText>
</LinearLayout>
<EditText
android:id="@+id/todo_edit_description"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="top"
android:hint="@string/todo_edit_description"
android:imeOptions="actionNext" >
</EditText>
<Button
android:id="@+id/todo_edit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/todo_edit_confirm" >
</Button>
</LinearLayout>
layout/todo_list.xml
<?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" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_todos" />
</LinearLayout>
layout/todo_row.xml
<?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">
<ImageView
android:id="@+id/icon"
android:layout_width="30dp"
android:layout_height="24dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:src="@mipmap/ic_launcher" >
</ImageView>
<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:lines="1"
android:text="@+id/TextView01"
android:textSize="24dp"
>
</TextView>
</LinearLayout>
(Etude de code)
menu/listmenu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/insert"
android:showAsAction="always"
android:title="Insert">
</item>
</menu>
values/strings.xml
<string name="hello">Hello World, Todo!</string>
<string name="no_todos">Currently there are no Todo items maintained</string>
<string name="menu_insert">Add Item</string>
<string name="menu_delete">Delete Todo</string>
<string name="todo_summary">Summary</string>
<string name="todo_description">Delete Todo</string>
<string name="todo_edit_summary">Summary</string>
<string name="todo_edit_description">Description</string>
<string name="todo_edit_confirm">Confirm</string>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.meuuh.mytodo" >
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".TodosOverviewActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".TodoDetailActivity"
android:windowSoftInputMode="stateVisible|adjustResize" >
</activity>
<provider
android:name=".contentprovider.MyTodoContentProvider"
android:authorities="com.meuuh.android.todos.contentprovider" >
</provider>
</application>
</manifest>
On résoult les bugs et on lance l'appli
Qui veux commenter le code ?
Fonctionne avec de nombreuse plateforme
Qu'est ce que Parse peut faire ?
C'est gratuit ! ... ou presque
Et surtout
Project name : Parse
Minimum API : 21
Type : Empty
Dans build.gradle (Module: app)
apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral()
maven { url 'https://maven.parse.com/repo' }
}
dependencies {
classpath 'com.parse.tools:gradle:1.+'
}
}
android { ... }
dependencies {
compile 'com.parse.bolts:bolts-android:1.2.1'
compile 'com.parse:parse-android:1.10.3'
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.0.1'
}
Créer une class ThisApplication étendu de Application
public class ThisApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Parse.enableLocalDatastore(this);
Parse.initialize(this, "application_id_key", "Client_key");
}
}
Dans AndroidManifest, on ajoute les permissions pour internet et l'accés au status du réseau (ACCESS_NETWORK_STATE)
De plus, dans <application>, on ajoute le name qui pointe vers ThisApplication
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="save"
android:id="@+id/button"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editText"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/button" />
</RelativeLayout>
On crée une classe Message qui est étendu de ParseObject
@ParseClassName("Message")
public class Message extends ParseObject{
public String getMessage() { return getString("message"); }
public void setMessage(String message) { put("message", message); }
}
Dans la classe MainActivity, quand on clique sur le button, on sauvegarde dans parse :
Message message = new Message();
message.setMessage(editText.getText().toString());
message.saveInBackground();
Text
On compile, on debug et on commente
ParseQuery<Message> query = ParseQuery.getQuery(Message.class);
query.findInBackground(new FindCallback<Message>() {
@Override
public void done(List<Message> objects, ParseException e) {
String text = "";
for (int i = 0; i < objects.size(); i++) {
text += objects.get(i).getMessage();
text += "\n";
}
textView.setText(text);
}
});
Ensuite, on récupérer les données de Parse et on les afficher
On améliore le code :
- Mise à jour de l'affichage à chaque clique
- Bloquer le mode paysage
- Enlever le clavier apres l'envoie
Lions les adapter avec les données
this.contacts.clear(); // Delete all data from l'ArrayList
this.contacts.addAll(contacts); // Copy all data
this.roomsAdapters.notifyDataSetChanged(); // prévient que l'adapter que les données lié on changé
recyclerView.invalidate(); // recrée l'affichage
Hint, il est possible de remplir complement un ArrayList et de prévenir l'adapter et le RecyclerView
Quand on lance ContactsActivity
- On récupère tous les contacts de Parse
- Si le numero, contenu dans l'intent, n'est pas dans la liste, on l'ajoute dans parse
- Si le numero, contenu dans l'intent, est dans la liste, on le retire avant de donner cette liste au RecyclerView
this.contacts.clear(); // Delete all data from l'ArrayList
this.contacts.addAll(contacts); // Copy all data
this.roomsAdapters.notifyDataSetChanged(); // prévient que l'adapter que les données lié on changé
recyclerView.invalidate(); // recrée l'affichage
Hint, il est possible de remplir complement un ArrayList et de prévenir l'adapter et le recyclerList
Quand on clique sur un contact :
- Il faut récupérer tous les messages dont la source et le recipient est en commun
ParseQuery<Message> q1 = ParseQuery.getQuery("Message");
q1.whereEqualTo("source", source.getNumber())
.whereEqualTo("recipient", recipient.getNumber());
ParseQuery<Message> q2 = ParseQuery.getQuery("Message");
q2.whereEqualTo("source", recipient.getNumber())
.whereEqualTo("recipient", source.getNumber());
List<ParseQuery<Message>> queries = new ArrayList<ParseQuery<Message>>();
queries.add(q1);
queries.add(q2);
ParseQuery<Message> mainQuery = ParseQuery.or(queries);
mainQuery.orderByAscending("createAt");
mainQuery.findInBackground(new FindCallback<Message>() {
public void done(List<Message> results, ParseException e) {
/// ....
}
});
- Requête pour demande tous les messages :
@ParseClassName("Message")
public class Message extends ParseObject{
public void setSource(String source) { put("source", source);}
public String getSource() { return getString("source"); }
public void setRecipient(String recipient) { put("recipient", recipient);}
public String getRecipient() { return getString("recipient"); }
public void setMessage(String message) { put("message", message); }
public String getMessage() { return getString("message"); }
}
Android nous permet de d'afficher des notifications dans la bar de notification. L'utilisateur peut les étendre ou cliquer sur la notification qui exécutera une activité.
La gestion des notification depuis l'application se fait grace au Notification Manager.
On peut l'appeler grace à getSystemService(NOTIFICATION_SERVICE) qui est dans présent dans les context / activité / Service.
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
Android nous permet de d'afficher des notifications dans la bar de notification. L'utilisateur peut les étendre ou cliquer sur la notification qui exécutera une activité.
La gestion des notification depuis l'application se fait grace au Notification Manager.
On peut l'appeler grace à getSystemService(NOTIFICATION_SERVICE) qui est dans présent dans les context / activité / Service.
NotificationManager notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
Intent intent = new Intent(this, NotificationReceiver.class);
// use System.currentTimeMillis() to have a unique ID for the pending intent
PendingIntent pIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), intent, 0);
// build notification
// the addAction re-use the same intent to keep the example short
Notification n = new Notification.Builder(this)
.setContentTitle("New mail from " + "test@gmail.com")
.setContentText("Subject")
.setSmallIcon(R.drawable.icon)
.setContentIntent(pIntent)
.setAutoCancel(true)
.addAction(R.drawable.icon, "Call", pIntent)
.addAction(R.drawable.icon, "More", pIntent)
.addAction(R.drawable.icon, "And more", pIntent).build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(0, n);
Pour envoyer et recevoir des messages
Créer un nouveau projet et générer un API key
https://console.developers.google.com/
Générer un fichier de configuration
https://developers.google.com/cloud-messaging/android/client
Copier le fichier dans app/
Dans build.gradle (Project)
[...]
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.google.gms:google-services:1.4.0-beta3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
[...]
Dans build.gradle (Module)
[...]
apply plugin: 'com.google.gms.google-services'
[...]
dependencies {
compile 'com.parse.bolts:bolts-android:1.2.1'
compile 'com.parse:parse-android:1.10.3'
compile 'com.squareup.picasso:picasso:2.5.0'
compile "com.android.support:recyclerview-v7:23.0.1"
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.google.android.gms:play-services-gcm:8.1.0'
}
MyGcmListenerService
public class MyGcmListenerService extends GcmListenerService {
private static final String TAG = "MyGcmListenerService";
/**
* Called when message is received.
*
* @param from SenderID of the sender.
* @param data Data bundle containing message data as key/value pairs.
* For Set of keys use data.keySet().
*/
// [START receive_message]
@Override
public void onMessageReceived(String from, Bundle data) {
String message = data.getString("message");
Log.d(TAG, "From: " + from);
Log.d(TAG, "Message: " + message);
if (from.startsWith("/topics/")) {
// message received from some topic.
} else {
// normal downstream message.
}
// [START_EXCLUDE]
/**
* Production applications would usually process the message here.
* Eg: - Syncing with server.
* - Store message in local database.
* - Update UI.
*/
/**
* In some cases it may be useful to show a notification indicating to the user
* that a message was received.
*/
sendNotification(message);
// [END_EXCLUDE]
}
// [END receive_message]
/**
* Create and show a simple notification containing the received GCM message.
*
* @param message GCM message received.
*/
private void sendNotification(String message) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle("GCM Message")
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
MyInstanceIDListenerService
import android.content.Intent;
import com.google.android.gms.iid.InstanceIDListenerService;
import com.meuuh.chatwithme.GCM.RegistrationIntentService;
public class MyInstanceIDListenerService extends InstanceIDListenerService {
private static final String TAG = "MyInstanceIDLS";
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. This call is initiated by the
* InstanceID provider.
*/
// [START refresh_token]
@Override
public void onTokenRefresh() {
// Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
// [END refresh_token]
}
MyInstanceIDListenerService
public class QuickstartPreferences {
public static final String SENT_TOKEN_TO_SERVER = "sentTokenToServer";
public static final String REGISTRATION_COMPLETE = "registrationComplete";
}
MyInstanceIDListenerService
import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.android.gms.gcm.GcmPubSub;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
import com.meuuh.chatwithme.R;
import java.io.IOException;
/**
* Created by Edouard on 15/10/2015.
*/
public class RegistrationIntentService extends IntentService {
private static final String TAG = "RegIntentService";
private static final String[] TOPICS = {"global"};
public RegistrationIntentService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
try {
// [START register_for_gcm]
// Initially this call goes out to the network to retrieve the token, subsequent calls
// are local.
// [START get_token]
InstanceID instanceID = InstanceID.getInstance(this);
String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
// [END get_token]
Log.i(TAG, "GCM Registration Token: " + token);
// TODO: Implement this method to send any registration to your app's servers.
sendRegistrationToServer(token);
// Subscribe to topic channels
subscribeTopics(token);
// You should store a boolean that indicates whether the generated token has been
// sent to your server. If the boolean is false, send the token to your server,
// otherwise your server should have already received the token.
sharedPreferences.edit().putBoolean(QuickstartPreferences.SENT_TOKEN_TO_SERVER, true).apply();
// [END register_for_gcm]
} catch (Exception e) {
Log.d(TAG, "Failed to complete token refresh", e);
// If an exception happens while fetching the new token or updating our registration data
// on a third-party server, this ensures that we'll attempt the update at a later time.
sharedPreferences.edit().putBoolean(QuickstartPreferences.SENT_TOKEN_TO_SERVER, false).apply();
}
// Notify UI that registration has completed, so the progress indicator can be hidden.
Intent registrationComplete = new Intent(QuickstartPreferences.REGISTRATION_COMPLETE);
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
}
/**
* Persist registration to third-party servers.
*
* Modify this method to associate the user's GCM registration token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private void sendRegistrationToServer(String token) {
// Add custom implementation, as needed.
}
/**
* Subscribe to any GCM topics of interest, as defined by the TOPICS constant.
*
* @param token GCM token
* @throws IOException if unable to reach the GCM PubSub service
*/
// [START subscribe_topics]
private void subscribeTopics(String token) throws IOException {
GcmPubSub pubSub = GcmPubSub.getInstance(this);
for (String topic : TOPICS) {
pubSub.subscribe(token, "/topics/" + topic, null);
}
}
// [END subscribe_topics]
}
AndroidManifest
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
[...]
<!-- [START gcm_receiver] -->
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.meuuh.chatwithme" />
</intent-filter>
</receiver>
<!-- [END gcm_receiver] -->
<!-- [START gcm_listener] -->
<service
android:name=".GCM.MyGcmListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
<!-- [END gcm_listener] -->
<!-- [START instanceId_listener] -->
<service
android:name=".GCM.MyInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID"/>
</intent-filter>
</service>
<!-- [END instanceId_listener] -->
<service
android:name=".GCM.RegistrationIntentService"
android:exported="false">
</service>
Télécharger et dezipper dans res/
De nos jours la plupart des téléphones ont une puce GPS. Android nous permet de demander sa position.
La classe LocationManager nous permet d'accéder à la location GPS du téléphone, de nous enregistrer a un location update listeners, à des alertes de proximité et plus encore.
Il existe plusieurs providers
Pour plus de facilité, la selection des providers peut se faire avec Criteria, qui choisira qui est le meilleurs provider.
// Get the location manager
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// Define the criteria how to select the locatioin provider -> use
// default
Criteria criteria = new Criteria();
provider = locationManager.getBestProvider(criteria, false);
Location location = locationManager.getLastKnownLocation(provider);
Il n'est pas possible d'activer le GPS directement dans le code, il faut que ca soit l'utilisateur qui l'active.
La meilleur méthode est de lancer un intent de la configuration GPS
LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean enabled = service
.isProviderEnabled(LocationManager.GPS_PROVIDER);
// check if enabled and if not send user to the GSP settings
// Better solution would be to display a dialog and suggesting to
// go to the settings
if (!enabled) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
N'oublions pas les permissions à ajouter dans le manifest
INTERNET
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
Project name : LocateMe
Minimum API : 17
Type : Empty
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
public class MainActivity extends AppCompatActivity implements LocationListener {
private TextView latituteField;
private TextView longitudeField;
private LocationManager locationManager;
private String provider;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
latituteField = (TextView) findViewById(R.id.TextView02);
longitudeField = (TextView) findViewById(R.id.TextView04);
// Get the location manager
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// Define the criteria how to select the locatioin provider -> use
// default
Criteria criteria = new Criteria();
provider = locationManager.getBestProvider(criteria, false);
Location location = locationManager.getLastKnownLocation(provider);
// Initialize the location fields
if (location != null) {
System.out.println("Provider " + provider + " has been selected.");
onLocationChanged(location);
} else {
latituteField.setText("Location not available");
longitudeField.setText("Location not available");
}
}
/* Request updates at startup */
@Override
protected void onResume() {
super.onResume();
locationManager.requestLocationUpdates(provider, 400, 1, this);
}
/* Remove the locationlistener updates when Activity is paused */
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(Location location) {
int lat = (int) (location.getLatitude());
int lng = (int) (location.getLongitude());
latituteField.setText(String.valueOf(lat));
longitudeField.setText(String.valueOf(lng));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
@Override
public void onProviderEnabled(String provider) {
Toast.makeText(this, "Enabled new provider " + provider,
Toast.LENGTH_SHORT).show();
}
@Override
public void onProviderDisabled(String provider) {
Toast.makeText(this, "Disabled provider " + provider,
Toast.LENGTH_SHORT).show();
}
}
<?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" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dip"
android:orientation="horizontal" >
<TextView
android:id="@+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="5dip"
android:text="Latitude: "
android:textSize="20dip" >
</TextView>
<TextView
android:id="@+id/TextView02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="unknown"
android:textSize="20dip" >
</TextView>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/TextView03"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="5dip"
android:text="Longitute: "
android:textSize="20dip" >
</TextView>
<TextView
android:id="@+id/TextView04"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="unknown"
android:textSize="20dip" >
</TextView>
</LinearLayout>
</LinearLayout>
BONUS