├── .gitignore ├── .idea ├── .name ├── checkstyle-idea.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── ListaAlumnosAndroid.iml ├── MyApplication.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── andres │ │ └── myapplication │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── example │ │ └── andres │ │ └── myapplication │ │ ├── Activities │ │ ├── AuthenticatorActivity.java │ │ ├── MainActivity.java │ │ └── StudentActivity.java │ │ ├── Authenticator │ │ └── AccountAuthenticator.java │ │ ├── Fragments │ │ ├── AddStudentDialogFragment.java │ │ ├── StudentFragment.java │ │ └── StudentsListFragment.java │ │ ├── Model │ │ ├── Item.java │ │ └── Student.java │ │ ├── Networking │ │ ├── GetRequest.java │ │ ├── PostRequest.java │ │ └── Request.java │ │ ├── Persistence │ │ ├── DatabaseContract.java │ │ └── StudentsDbHelper.java │ │ ├── Provider │ │ ├── StudentsContract.java │ │ └── StudentsProvider.java │ │ ├── Services │ │ ├── AuthenticatorService.java │ │ └── SyncService.java │ │ ├── StudentAdapter.java │ │ └── SyncAdapter │ │ └── SyncAdapter.java │ └── res │ ├── drawable │ ├── button_action_bar.png │ ├── ic_launcher.png │ └── sync_button.png │ ├── layout-land │ └── activity_main.xml │ ├── layout │ ├── activity_authenticator.xml │ ├── activity_main.xml │ ├── activity_student.xml │ ├── dialog_agregar_alumno.xml │ ├── fragment_agregar_alumno_dialog.xml │ ├── fragment_list.xml │ ├── fragment_main_activity2.xml │ ├── fragment_student.xml │ ├── fragment_student2.xml │ ├── list_item.xml │ └── list_item_2.xml │ ├── menu │ ├── menu_main.xml │ └── menu_student.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── authenticator.xml │ └── syncadapter.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images-tutorial ├── ContentProviderGeneral.png ├── SyncAdapterSchema.png ├── agregar_cuenta_config.png ├── app_en_accounts_settings.png ├── flujo_authenticator.png └── provider_uri_explicacion.png ├── settings.gradle └── tutorial.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ListaAlumnosAndroid -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ListaAlumnosAndroid.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /MyApplication.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial SyncAdapter 2 | 3 | También se puede ver este tutorial en PDF: [Tutorial PDF](https://github.com/aamatte/Tutorial-Sync-Adapter/raw/master/tutorial.pdf). 4 | 5 | La consultora Gartner estima que para 2016 el 40% de las aplicaciones 6 | móviles usará servicios de la nube. Esto claramente obliga a los 7 | desarrolladores a aprender y a utilizar las herramientas que nos 8 | proporcionan las distintos plataformas para utilizar estos servicios de 9 | manera eficiente. Si bien cada desarrollador puede implementar su propio 10 | sistema de sincronización de datos, no hay necesidad de reinventar la 11 | rueda. Android nos ofrece el SyncAdapter, un componente complejo pero poderoso que nos 12 | ayuda a manejar y automatizar estas transferencias. 13 | 14 | Beneficios 15 | ---------- 16 | 17 | 1. Ejecución automatizada: la sincronización estará definida de acuerdo 18 | a tu configuración y se ejecutará automáticamente de acuerdo a eso. 19 | Con esto podemos eliminar el botón de refresh. 20 | 21 | 2. Revisión de conectividad: por ejemplo, si en algún momento no hay 22 | conexión a internet y corresponde una sincronización el framework se 23 | encargará de posponer la sincronización. 24 | 25 | 3. Encolación de transferencias fallidas: si se está descargando un 26 | archivo y esto falla, se vuelve a intentar. 27 | 28 | 4. Gasta menos batería: muchas veces el framework realizará la 29 | sincronización de más de una aplicación al mismo tiempo. De esta 30 | forma la antena celular se enciende con menor frecuencia. 31 | 32 | 5. Centralización de la transferencia: la sincronización de los datos 33 | se realiza toda al mismo tiempo y en el mismo lugar. 34 | 35 | 6. Manejo de cuenta y autenticación: si el usuario de tu aplicación 36 | requiere credenciales especiales se puede integrar el manejo de 37 | cuentas y autenticación en la transferencia. 38 | 39 | Qué haremos 40 | ----------- 41 | 42 | #### 43 | 44 | Durante el siguiente tutorial se utilizará una aplicación sencilla que 45 | liste a los integrantes de un curso. Se podrá añadir estudiantes, tanto 46 | en el servidor web como en la aplicación android, y se deberán mantener 47 | sincronizados los datos a través de un sync adapter. No se entrará en 48 | temas que escapan del objetivo del tutorial como el servidor, las vistas 49 | o sobre como se realizan los requests. De todas formas se puede revisar 50 | el código fuente de la aplicación en este repositorio. 51 | 52 | Qué se necesita 53 | --------------- 54 | 55 | #### 56 | 57 | Para que nuestro SyncAdapter esté funcionando necesitamos cubrir e 58 | implementar los siguientes componentes o funcionalidades: 59 | 60 | 1. [Base de datos](https://github.com/aamatte/EjemploSyncAdapter/wiki/Base-de-datos) 61 | 62 | 2. [Content provider](https://github.com/aamatte/EjemploSyncAdapter/wiki/Content-provider) 63 | 64 | 3. [Authenticator](https://github.com/aamatte/EjemploSyncAdapter/wiki/Authenticator) 65 | 66 | 4. [Clase SyncAdapter](https://github.com/aamatte/EjemploSyncAdapter/wiki/SyncAdapter) 67 | 68 | 5. [Correr el SyncAdapter](https://github.com/aamatte/EjemploSyncAdapter/wiki/Correr-el-SyncAdapter) 69 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.2" 6 | 7 | defaultConfig { 8 | applicationId "com.example.andres.myapplication" 9 | minSdkVersion 16 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.0.0' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/andres/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/andres/myapplication/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 34 | 35 | 36 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Activities/AuthenticatorActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Activities; 2 | 3 | import android.accounts.Account; 4 | import android.accounts.AccountAuthenticatorActivity; 5 | import android.accounts.AccountManager; 6 | import android.content.Intent; 7 | import android.os.AsyncTask; 8 | import android.os.Bundle; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | import com.example.andres.myapplication.Authenticator.AccountAuthenticator; 14 | import com.example.andres.myapplication.R; 15 | 16 | /** 17 | * Actividad llamada por AccountAuthenticator para que el usuario ingrese a su cuenta o se registre. 18 | * Esta debe interactuar con el servidor para obtener el token y retornarlo al AccountAuthenticator. 19 | */ 20 | public class AuthenticatorActivity extends AccountAuthenticatorActivity { 21 | 22 | public static final String ARG_ACCOUNT_TYPE = "1"; 23 | public static final String ARG_AUTH_TYPE = "2"; 24 | public static final String ARG_IS_ADDING_NEW_ACCOUNT = "3"; 25 | public static final String PARAM_USER_PASS = "4"; 26 | private AccountManager mAccountManager; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_authenticator); 32 | mAccountManager = AccountManager.get(this); 33 | Button button = (Button) findViewById(R.id.button); 34 | button.setOnClickListener(new View.OnClickListener() { 35 | @Override 36 | public void onClick(View v) { 37 | submit(); 38 | } 39 | }); 40 | } 41 | 42 | /** 43 | * Método llamado cuando se aprieta el botón de login. Manda al servidor el usuario y contraseña 44 | * y este le retorna el token necesario para utilizar la API. 45 | */ 46 | private void submit() { 47 | // Se obtiene el usuario y contrasena ingresados 48 | final String userName = ((TextView) findViewById(R.id.account_name)) 49 | .getText().toString(); 50 | final String userPass = ((TextView) findViewById(R.id.account_password)) 51 | .getText().toString(); 52 | 53 | // Se loguea de forma asincronica para no entorpecer el UI thread 54 | new AsyncTask() { 55 | @Override 56 | protected Intent doInBackground(Void... params) { 57 | // Se loguea en el servidor y retorna token 58 | String authtoken = logIn(userName, userPass); 59 | // Informacion necesaria para enviar al authenticator 60 | final Intent res = new Intent(); 61 | res.putExtra(AccountManager.KEY_ACCOUNT_NAME, userName); 62 | res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE); 63 | res.putExtra(AccountManager.KEY_AUTHTOKEN, authtoken); 64 | res.putExtra(PARAM_USER_PASS, userPass); 65 | return res; 66 | } 67 | @Override 68 | protected void onPostExecute(Intent intent) { 69 | finishLogin(intent); 70 | } 71 | }.execute(); 72 | } 73 | 74 | /** 75 | * Crea cuenta en el AccountManager si es que aún no existe y setea token asociado a esa cuenta. 76 | * Si cuenta ya existe, solo setea nueva password. Luego, finaliza esta actividad. 77 | * @param intent 78 | */ 79 | private void finishLogin(Intent intent) { 80 | 81 | String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); 82 | String accountPassword = intent.getStringExtra(PARAM_USER_PASS); 83 | final Account account = new Account(accountName, 84 | intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE)); 85 | 86 | // Si es que se esta anadiendo una nueva cuenta 87 | if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) { 88 | 89 | String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); 90 | String authtokenType = AccountAuthenticator.AUTHTOKEN_TYPE; 91 | // Creando cuenta en el dispositivo y seteando el token que obtuvimos. 92 | mAccountManager.addAccountExplicitly(account, accountPassword, null); 93 | 94 | // Ojo: hay que setear el token explicitamente si la cuenta no existe, 95 | // no basta con mandarlo al authenticator 96 | mAccountManager.setAuthToken(account, authtokenType, authtoken); 97 | } 98 | // Si no se está añadiendo cuenta, el token estaba antiguo invalidado. 99 | // Seteamos contraseña nueva por si la cambio. 100 | else { 101 | // Solo seteamos contraseña. Aca no es necesario setear el token explicitamente, 102 | // basta con enviarlo al Authenticator 103 | mAccountManager.setPassword(account, accountPassword); 104 | } 105 | // Setea el resultado para que lo reciba el Authenticator 106 | setAccountAuthenticatorResult(intent.getExtras()); 107 | setResult(RESULT_OK, intent); 108 | // Cerramos la actividad 109 | finish(); 110 | } 111 | 112 | /** 113 | * Pide al servidor el token asociado al usuario y contraseña lo retorna. 114 | * @param user Username. 115 | * @param pass Password. 116 | * @return Token necesario para interactuar con la API. 117 | */ 118 | private String logIn(String user, String pass){ 119 | // Método para fines demostrativos :) 120 | return "tokentokentokentoken"; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Activities; 2 | 3 | import android.accounts.Account; 4 | import android.accounts.AccountManager; 5 | import android.accounts.AccountManagerCallback; 6 | import android.accounts.AccountManagerFuture; 7 | import android.app.DialogFragment; 8 | import android.content.ContentResolver; 9 | import android.content.Intent; 10 | import android.database.ContentObserver; 11 | import android.net.Uri; 12 | import android.os.Bundle; 13 | import android.os.Handler; 14 | import android.support.v7.app.ActionBarActivity; 15 | import android.view.Menu; 16 | import android.view.MenuItem; 17 | 18 | import com.example.andres.myapplication.Authenticator.AccountAuthenticator; 19 | import com.example.andres.myapplication.Fragments.AddStudentDialogFragment; 20 | import com.example.andres.myapplication.Fragments.StudentsListFragment; 21 | import com.example.andres.myapplication.Fragments.StudentFragment; 22 | import com.example.andres.myapplication.Model.Item; 23 | import com.example.andres.myapplication.Model.Student; 24 | import com.example.andres.myapplication.Provider.StudentsContract; 25 | import com.example.andres.myapplication.R; 26 | 27 | public class MainActivity extends ActionBarActivity implements 28 | StudentsListFragment.OnFragmentInteractionListener, 29 | AddStudentDialogFragment.NoticeDialogListener { 30 | public static final String CODE_NAME = "name"; 31 | 32 | private AccountManager mAccountManager; 33 | private Account mAccount; 34 | public static String token = ""; 35 | 36 | private AccountManagerCallback mGetAuthTokenCallback = 37 | new AccountManagerCallback() { 38 | @Override 39 | public void run(final AccountManagerFuture arg0) { 40 | try { 41 | token = (String) arg0.getResult().get(AccountManager.KEY_AUTHTOKEN); 42 | } catch (Exception e) { 43 | // handle error 44 | } 45 | } 46 | }; 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | ContentResolver resolver = getContentResolver(); 52 | 53 | mAccountManager = (AccountManager) getSystemService( 54 | ACCOUNT_SERVICE); 55 | 56 | // Se chequea si existe una cuenta asociada a ACCOUNT_TYPE. 57 | Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); 58 | if (accounts.length == 0){ 59 | // También se puede llamar a metodo mAccountManager.addAcount(...) 60 | Intent intent = new Intent(this, AuthenticatorActivity.class); 61 | intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true); 62 | startActivity(intent); 63 | } 64 | else { 65 | mAccount = accounts[0]; 66 | mAccountManager.getAuthToken(mAccount, AccountAuthenticator.ACCOUNT_TYPE, null, this, 67 | mGetAuthTokenCallback, null); 68 | resolver.setIsSyncable(mAccount, StudentsContract.AUTHORITY, 1); 69 | resolver.setSyncAutomatically(mAccount, StudentsContract.AUTHORITY, true); 70 | } 71 | 72 | TableObserver observer = new TableObserver(null); 73 | /* 74 | * Registra el obsever para students 75 | */ 76 | resolver.registerContentObserver(StudentsContract.STUDENTS_URI, true, observer); 77 | setContentView(R.layout.activity_main); 78 | } 79 | 80 | /** 81 | * Escucha los cambios que hayan en 82 | * {@link com.example.andres.myapplication.Provider.StudentsProvider}. 83 | */ 84 | public class TableObserver extends ContentObserver { 85 | 86 | public TableObserver(Handler handler) { 87 | super(handler); 88 | } 89 | 90 | /** 91 | * Define el método que es llamado cuando los datos en el content provider cambian. 92 | * Este método es solo para que haya compatibilidad con plataformas más viejas. 93 | */ 94 | @Override 95 | public void onChange(boolean selfChange) { 96 | onChange(selfChange, null); 97 | } 98 | /** 99 | * Define el método que es llamado cuando los datos en el content provider cambian. 100 | */ 101 | @Override 102 | public void onChange(boolean selfChange, Uri changeUri) { 103 | 104 | if (mAccount != null) { 105 | // Corre la sincronizacion 106 | ContentResolver.requestSync(mAccount, StudentsContract.AUTHORITY, null); 107 | } 108 | } 109 | } 110 | 111 | @Override 112 | public boolean onCreateOptionsMenu(Menu menu) { 113 | // Inflate the menu; this adds items to the action bar if it is present. 114 | getMenuInflater().inflate(R.menu.menu_main, menu); 115 | return super.onCreateOptionsMenu(menu); 116 | } 117 | 118 | @Override 119 | public boolean onOptionsItemSelected(MenuItem item) { 120 | // Handle action bar item clicks here. The action bar will 121 | // automatically handle clicks on the Home/Up button, so long 122 | // as you specify a parent activity in AndroidManifest.xml. 123 | int id = item.getItemId(); 124 | 125 | //noinspection SimplifiableIfStatement 126 | if (id == R.id.action_settings) { 127 | return true; 128 | } 129 | else if (id == R.id.action_agregar) { 130 | AddStudentDialogFragment dialog = new AddStudentDialogFragment(); 131 | dialog.show(this.getFragmentManager(), "dialog"); 132 | } 133 | return super.onOptionsItemSelected(item); 134 | } 135 | 136 | /** 137 | * Abre una nueva actividad con un StudentFragment dentro de ella que muestra el nombre del 138 | * estudiante seleccionado. 139 | * @param item Estudiante seleccionado. 140 | */ 141 | @Override 142 | public void onFragmentInteractionList(Item item) { 143 | String name = item.getText(); 144 | StudentFragment studentFrag = (StudentFragment) getSupportFragmentManager() 145 | .findFragmentById(R.id.student_fragment); 146 | if (studentFrag != null && studentFrag.isVisible()) { 147 | studentFrag.setName(item.getText()); 148 | } 149 | else { 150 | Intent intent = new Intent(this,StudentActivity.class); 151 | intent.putExtra(CODE_NAME, name); 152 | startActivity(intent); 153 | } 154 | } 155 | 156 | @Override 157 | public void onGetStudentsFromCloud() { } 158 | 159 | /** 160 | * Llamado cuando se presinó el botón positivo en el dialogo de añadir estudiante. 161 | * @param dialog 162 | * @param student 163 | */ 164 | @Override 165 | public void onDialogPositiveClick(DialogFragment dialog, Student student) { 166 | StudentsListFragment listFrag = (StudentsListFragment) 167 | getSupportFragmentManager().findFragmentById(R.id.fragment_list); 168 | if (listFrag == null) return; 169 | listFrag.addStudent(student); 170 | } 171 | 172 | @Override 173 | public void onDialogNegativeClick(DialogFragment dialog) { } 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Activities/StudentActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.ActionBarActivity; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | 8 | import com.example.andres.myapplication.R; 9 | import com.example.andres.myapplication.Fragments.StudentFragment; 10 | 11 | 12 | public class StudentActivity extends ActionBarActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_student); 18 | 19 | Bundle args = new Bundle(); 20 | String name = getIntent().getStringExtra(MainActivity.CODE_NAME); 21 | args.putString(MainActivity.CODE_NAME, name); 22 | 23 | StudentFragment fragment = new StudentFragment(); 24 | fragment.setArguments(args); 25 | if (savedInstanceState == null) { 26 | getSupportFragmentManager().beginTransaction() 27 | .add(R.id.container, fragment) 28 | .commit(); 29 | } 30 | } 31 | 32 | @Override 33 | public boolean onCreateOptionsMenu(Menu menu) { 34 | // Inflate the menu; this adds items to the action bar if it is present. 35 | getMenuInflater().inflate(R.menu.menu_student, menu); 36 | return true; 37 | } 38 | 39 | @Override 40 | public boolean onOptionsItemSelected(MenuItem item) { 41 | // Handle action bar item clicks here. The action bar will 42 | // automatically handle clicks on the Home/Up button, so long 43 | // as you specify a parent activity in AndroidManifest.xml. 44 | int id = item.getItemId(); 45 | //noinspection SimplifiableIfStatement 46 | if (id == R.id.action_settings) { 47 | return true; 48 | } 49 | return super.onOptionsItemSelected(item); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Authenticator/AccountAuthenticator.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Authenticator; 2 | 3 | import android.accounts.AbstractAccountAuthenticator; 4 | import android.accounts.Account; 5 | import android.accounts.AccountAuthenticatorResponse; 6 | import android.accounts.AccountManager; 7 | import android.accounts.NetworkErrorException; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.os.Bundle; 11 | import android.text.TextUtils; 12 | 13 | import com.example.andres.myapplication.Activities.AuthenticatorActivity; 14 | 15 | /** 16 | * Clase que maneja la autenticación y realiza la gran mayoría de las operaciones importantes 17 | * de una cuenta. 18 | */ 19 | public class AccountAuthenticator extends AbstractAccountAuthenticator { 20 | public static final String ACCOUNT_TYPE = "com.example.andres.myapplication"; 21 | public static final String AUTHTOKEN_TYPE = "normal"; 22 | private Context mContext; 23 | 24 | public AccountAuthenticator(Context context) { 25 | super(context); 26 | mContext = context; 27 | } 28 | 29 | @Override 30 | public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { 31 | return null; 32 | } 33 | 34 | /** 35 | * Llamado cuando el usuario quiere loguearse y añadir un nuevo usuario. 36 | * @param response 37 | * @param accountType 38 | * @param authTokenType 39 | * @param requiredFeatures 40 | * @param options 41 | * @return bundle con intent para iniciar AuthenticatorActivity. 42 | * @throws NetworkErrorException 43 | */ 44 | @Override 45 | public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 46 | String authTokenType, String[] requiredFeatures, Bundle options) 47 | throws NetworkErrorException { 48 | 49 | final Intent intent = new Intent(mContext, AuthenticatorActivity.class); 50 | intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType); 51 | intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType); 52 | intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true); 53 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); 54 | final Bundle bundle = new Bundle(); 55 | bundle.putParcelable(AccountManager.KEY_INTENT, intent); 56 | return bundle; 57 | } 58 | 59 | @Override 60 | public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, 61 | Bundle options) throws NetworkErrorException { 62 | return null; 63 | } 64 | 65 | /** 66 | * Obtiene el token de una cuenta. Si falla, se avisa que se debe llamar a AuthenticatorActivity. 67 | * @return Si resulta, bundle con informacion de cuenta y token. Si falla, bundle con 68 | * informacion de cuenta y activity. 69 | */ 70 | @Override 71 | public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, 72 | String authTokenType, Bundle options) throws NetworkErrorException { 73 | 74 | // Extrae username y pass del account manager 75 | final AccountManager am = AccountManager.get(mContext); 76 | 77 | // Pide el authtoken 78 | String authToken = am.peekAuthToken(account, authTokenType); 79 | 80 | // Intento de autenticar al usuario 81 | if (TextUtils.isEmpty(authToken)){ 82 | final String password = am.getPassword(account); 83 | if (password != null) { 84 | // Se autentica en el servidor 85 | authToken = authenticateInServer(account); 86 | } 87 | } 88 | // Si obtenemos un authToken, lo retornamos 89 | if (!TextUtils.isEmpty(authToken)) { 90 | 91 | final Bundle result = new Bundle(); 92 | result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); 93 | result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); 94 | result.putString(AccountManager.KEY_AUTHTOKEN, authToken); 95 | 96 | return result; 97 | } 98 | // Si llegamos acá aún no podemos obtener el token. 99 | // Necesitamos pedirle de nuevo las credenciales 100 | final Intent intent = new Intent(mContext, AuthenticatorActivity.class); 101 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); 102 | intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type); 103 | intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType); 104 | 105 | final Bundle bundle = new Bundle(); 106 | bundle.putParcelable(AccountManager.KEY_INTENT, intent); 107 | return bundle; 108 | } 109 | 110 | @Override 111 | public String getAuthTokenLabel(String authTokenType) { 112 | return null; 113 | } 114 | 115 | @Override 116 | public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, 117 | String authTokenType, Bundle options) 118 | throws NetworkErrorException { 119 | return null; 120 | } 121 | 122 | @Override 123 | public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, 124 | String[] features) throws NetworkErrorException { 125 | return null; 126 | } 127 | 128 | private String authenticateInServer(Account account){ 129 | // Método para fines de demostración :) 130 | return "tokentokentokentokentoken"; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Fragments/AddStudentDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Fragments; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.Dialog; 6 | import android.app.DialogFragment; 7 | import android.content.DialogInterface; 8 | import android.os.Bundle; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.widget.EditText; 12 | 13 | import com.example.andres.myapplication.Model.Student; 14 | import com.example.andres.myapplication.R; 15 | 16 | /** 17 | * Dialogo usado para el ingreso de los datos de un nuevo estudiante. 18 | * Se puede agregar un estudiante con nombre, apellido paterno y apellido materno. 19 | */ 20 | public class AddStudentDialogFragment extends DialogFragment { 21 | 22 | private NoticeDialogListener mListener; 23 | 24 | public interface NoticeDialogListener { 25 | void onDialogPositiveClick(DialogFragment dialog, Student student); 26 | void onDialogNegativeClick(DialogFragment dialog); 27 | } 28 | 29 | @Override 30 | public Dialog onCreateDialog(Bundle savedInstanceState) { 31 | // Use the Builder class for convenient dialog construction 32 | LayoutInflater inflater = getActivity().getLayoutInflater(); 33 | final View v = inflater.inflate(R.layout.dialog_agregar_alumno, null); 34 | 35 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 36 | builder.setView(v); 37 | builder.setTitle(R.string.dialog_add_student_title); 38 | 39 | builder.setPositiveButton(R.string.dialog_add_student_positive, new DialogInterface.OnClickListener() { 40 | public void onClick(DialogInterface dialog, int id) { 41 | String name = ((EditText) v.findViewById(R.id.dialog_student_name)) 42 | .getText().toString().toUpperCase(); 43 | String firstLastname = ((EditText) v.findViewById(R.id.dialog_first_lastname)) 44 | .getText().toString().toUpperCase(); 45 | String secondLastname = ((EditText) v.findViewById(R.id.dialog_second_lastname)) 46 | .getText().toString().toUpperCase(); 47 | Student student = new Student(name, firstLastname, secondLastname); 48 | mListener.onDialogPositiveClick(AddStudentDialogFragment.this , student); 49 | } 50 | }); 51 | 52 | builder.setNegativeButton(R.string.dialog_add_student_negative, new DialogInterface.OnClickListener() { 53 | public void onClick(DialogInterface dialog, int id) { 54 | // No pasa nada 55 | } 56 | }); 57 | // Create the AlertDialog object and return it 58 | return builder.create(); 59 | } 60 | 61 | @Override 62 | public void onAttach(Activity activity) { 63 | super.onAttach(activity); 64 | // Verify that the host activity implements the callback interface 65 | try { 66 | // Instantiate the NoticeDialogListener so we can send events to the host 67 | mListener = (NoticeDialogListener) activity; 68 | } catch (ClassCastException e) { 69 | // The activity doesn't implement the interface, throw exception 70 | throw new ClassCastException(activity.toString() 71 | + " must implement NoticeDialogListener"); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Fragments/StudentFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Fragments; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import com.example.andres.myapplication.Activities.MainActivity; 11 | import com.example.andres.myapplication.R; 12 | 13 | /** 14 | * Fragment que solo muestra el nombre del estudiante seleccionado. 15 | */ 16 | public class StudentFragment extends Fragment { 17 | 18 | private TextView mNameView; 19 | 20 | public StudentFragment() { 21 | // Required empty public constructor 22 | } 23 | 24 | @Override 25 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 26 | Bundle savedInstanceState) { 27 | // Inflate the layout for this fragment 28 | View v = inflater.inflate(R.layout.fragment_student, container, false); 29 | Bundle args = getArguments(); 30 | mNameView = (TextView) v.findViewById(R.id.text_view); 31 | mNameView.setText(R.string.select_a_student); 32 | if (args != null){ 33 | mNameView.setText(args.getString(MainActivity.CODE_NAME)); 34 | } 35 | return v; 36 | } 37 | 38 | public void setName(String name){ 39 | if (mNameView !=null) 40 | mNameView.setText(name); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Fragments/StudentsListFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Fragments; 2 | 3 | import android.app.Activity; 4 | import android.content.ContentValues; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.support.v4.app.Fragment; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.AdapterView; 14 | import android.widget.ListView; 15 | 16 | import com.example.andres.myapplication.Model.Item; 17 | import com.example.andres.myapplication.Model.Student; 18 | import com.example.andres.myapplication.StudentAdapter; 19 | import com.example.andres.myapplication.Persistence.DatabaseContract; 20 | import com.example.andres.myapplication.Persistence.StudentsDbHelper; 21 | import com.example.andres.myapplication.Provider.StudentsContract; 22 | import com.example.andres.myapplication.R; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.Comparator; 27 | 28 | /** 29 | * Fragment que se encarga de mostrar la lista de estudiantes guardada localmente 30 | * y controla los inputs del usuario. 31 | */ 32 | public class StudentsListFragment extends Fragment { 33 | 34 | private OnFragmentInteractionListener mListener; 35 | private ArrayList mIndexedItemArray = new ArrayList<>(); 36 | private ArrayList mStudents; 37 | private StudentAdapter mStudentsAdapter; 38 | private ListView mStudentsView; 39 | private StudentsDbHelper mDbHelper; 40 | 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 43 | Bundle savedInstanceState) { 44 | View v = inflater.inflate(R.layout.fragment_list, container, false); 45 | mStudentsAdapter = new StudentAdapter(v.getContext() , R.layout.list_item , 46 | mIndexedItemArray); 47 | mStudents = new ArrayList<>(); 48 | if (!getStudentsDb()){ 49 | mListener.onGetStudentsFromCloud(); 50 | } 51 | // Inflate the layout for this fragment 52 | mStudentsView = (ListView) v.findViewById(R.id.list_students); 53 | mStudentsView.setAdapter(mStudentsAdapter); 54 | mStudentsView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 55 | @Override 56 | public void onItemClick(AdapterView parent, View view, int position, long id) { 57 | // Solo se considera el click cuando se hace sobre una celda que es de del tipo 58 | // estudiante, no cuando es una letra de la indexación. 59 | if (mStudentsAdapter.getItem(position).getCellType() == Item.STUDENT_CELL) { 60 | mListener.onFragmentInteractionList(mStudentsAdapter.getItem(position)); 61 | } 62 | } 63 | }); 64 | return v; 65 | } 66 | 67 | /** 68 | * Agrega estudiante a la base de datos. Método usado cuando se obtiene un estudiante del 69 | * servidor. 70 | * @param student 71 | */ 72 | public void addStudent(Student student) { 73 | if (!studentInDb(student)) return; 74 | addStudentToIndexedArray(student); 75 | addStudentToDb(student); 76 | } 77 | 78 | /** 79 | * Agrega estudiante a la lista y regenera la lista indexada. 80 | * @param student 81 | */ 82 | private void addStudentToIndexedArray(Student student) { 83 | mStudents.add(student); 84 | generateIndexedItemArray(); 85 | } 86 | 87 | /** 88 | * Agrega estudiante a la base de datos local. 89 | * @param student 90 | * @return True si se pudo insertar, falso si no. 91 | */ 92 | private boolean addStudentToDb(Student student) { 93 | if (mDbHelper == null) { 94 | mDbHelper = StudentsDbHelper.getInstance(getActivity()); 95 | } 96 | ContentValues values = new ContentValues(); 97 | values.put(StudentsContract.StudentsColumns.NAMES, student.getNames()); 98 | values.put(StudentsContract.StudentsColumns.FIRST_LASTNAME, student.getFirstLastname()); 99 | values.put(StudentsContract.StudentsColumns.SECOND_LASTNAME, student.getSecondLastname()); 100 | values.put(StudentsContract.StudentsColumns.ID_CLOUD, student.getIdCloud()); 101 | 102 | Uri newUri = getActivity().getContentResolver().insert( 103 | StudentsContract.STUDENTS_URI, // the user dictionary content URI 104 | values // the values to insert 105 | ); // Valores 106 | return newUri != null; 107 | } 108 | 109 | /** 110 | * 111 | * @return 112 | */ 113 | private boolean getStudentsDb() { 114 | if (mDbHelper == null) { 115 | mDbHelper = StudentsDbHelper.getInstance(getActivity()); 116 | } 117 | // Selecciono todas las columnas. Podría dejarse como null. 118 | String[] projection = {DatabaseContract.Students.COLUMN_NAME_STUDENT_NAMES, 119 | DatabaseContract.Students.COLUMN_NAME_FIRST_LASTNAME, 120 | DatabaseContract.Students.COLUMN_NAME_SECOND_LASTNAME, 121 | DatabaseContract.Students.COLUMN_ID_CLOUD 122 | }; 123 | 124 | // Se deja como null porque no se requiere filtrar. Un ejemplo podria ser asi: 125 | // String selection = DatabaseContract.Students.COLUMN_NAME_FIRST_LASTNAME + "= ?" 126 | String selection = null; 127 | 128 | // Podria ser: 129 | // String[] selectionArgs = new String[]{ "Apellido" }; 130 | String[] selectionArgs = null; 131 | 132 | SQLiteDatabase db = mDbHelper.getReadableDatabase(); 133 | 134 | Cursor c = db.query(DatabaseContract.Students.TABLE_NAME, // Tabla 135 | projection, // Columnas a retornar 136 | selection, // Columnas de WHERE 137 | selectionArgs, // Valores de WHERE 138 | null, // Group by 139 | null, // Filtro por columnas de grupos 140 | DatabaseContract.Students.COLUMN_NAME_FIRST_LASTNAME +" ASC"); //Sort order 141 | 142 | ArrayList studentsInDb = new ArrayList<>(); 143 | c.moveToFirst(); 144 | if (c.getCount()<1) { 145 | return false; 146 | } 147 | while (c.moveToNext()) { 148 | String names = c.getString(c.getColumnIndexOrThrow( 149 | DatabaseContract.Students.COLUMN_NAME_STUDENT_NAMES)).toUpperCase(); 150 | String firstLast = c.getString(c.getColumnIndexOrThrow( 151 | DatabaseContract.Students.COLUMN_NAME_FIRST_LASTNAME)).toUpperCase(); 152 | String secondLast = c.getString(c.getColumnIndexOrThrow( 153 | DatabaseContract.Students.COLUMN_NAME_SECOND_LASTNAME)).toUpperCase(); 154 | int idCloud = c.getInt(c.getColumnIndexOrThrow( 155 | DatabaseContract.Students.COLUMN_ID_CLOUD)); 156 | studentsInDb.add(new Student(names, firstLast, secondLast, idCloud)); 157 | } 158 | if (studentsInDb.size()>0) { 159 | mStudents = studentsInDb; 160 | generateIndexedItemArray(); 161 | return true; 162 | } 163 | return false; 164 | } 165 | 166 | /** 167 | * Genera la lista que se mostrará en pantalla. La lista está ordenada por apellido paterno. 168 | * Contiene además de los estudiantes, celdas con las letras de inicio del apellido del 169 | * estudiante. Por ejemplo, se mostrará así: 170 | * 171 | * A 172 | * Pedro Aguirre 173 | * Juan Andrade 174 | * B 175 | * ... 176 | */ 177 | private void generateIndexedItemArray() { 178 | ArrayList items = new ArrayList<>(); 179 | Collections.sort(mStudents, new Student()); 180 | char last = mStudents.get(0).getFirstLastname().charAt(0); 181 | items.add(new Item(last + "", 0)); 182 | 183 | for (int i=0; i< mStudents.size(); i++) { 184 | Student s = mStudents.get(i); 185 | if (last != s.getFirstLastname().charAt(0)) { 186 | last = s.getFirstLastname().charAt(0); 187 | Item item = new Item(last+"", 0); 188 | items.add(item); 189 | } 190 | items.add(new Item(s.getNames() + " " + s.getFirstLastname() + " " + 191 | s.getSecondLastname() + " ", 1)); 192 | } 193 | mIndexedItemArray = items; 194 | mStudentsAdapter.clear(); 195 | mStudentsAdapter.addAll(items); 196 | mStudentsAdapter.notifyDataSetChanged(); 197 | } 198 | 199 | @Override 200 | public void onAttach(Activity activity) { 201 | super.onAttach(activity); 202 | try { 203 | mListener = (OnFragmentInteractionListener) activity; 204 | } catch (ClassCastException e) { 205 | throw new ClassCastException(activity.toString() 206 | + " must implement OnFragmentInteractionListener"); 207 | } 208 | } 209 | 210 | /** 211 | * Determina si un estudiante ya está en la base de datos. 212 | * @param student 213 | * @return True si el estudiante ya está, false si no. 214 | */ 215 | private boolean studentInDb(Student student) { 216 | for (int i=0; i< mStudents.size(); i++){ 217 | if (mStudents.get(i).equals(student)) { 218 | return false; 219 | } 220 | } 221 | return true; 222 | } 223 | 224 | @Override 225 | public void onDetach() { 226 | super.onDetach(); 227 | mListener = null; 228 | } 229 | 230 | public interface OnFragmentInteractionListener { 231 | void onFragmentInteractionList(Item item); 232 | void onGetStudentsFromCloud(); 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Model/Item.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Model; 2 | 3 | public class Item { 4 | public final static int STUDENT_CELL = 1; 5 | public final static int LETTER_CELL = 0; 6 | 7 | private String mText; 8 | private int mCellType; 9 | 10 | public Item(String text, int cellType) { 11 | mText = text; 12 | mCellType = cellType; 13 | } 14 | 15 | public int getCellType() { 16 | return mCellType; 17 | } 18 | 19 | public String getText() { 20 | return mText; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Model/Student.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Model; 2 | 3 | import java.util.Comparator; 4 | 5 | public class Student implements Comparator{ 6 | 7 | public static final String NAME = "name"; 8 | public static final String FIRST_LASTNAME = "first_lastname"; 9 | public static final String SECOND_LASTNAME = "second_lastname"; 10 | public static final String ID = "id"; 11 | 12 | private String mNames; 13 | private String mFirstLastname; 14 | private String mSecondLastname; 15 | private int mIdCloud; 16 | 17 | public Student() { } 18 | 19 | public Student(String names, String firstLastname, String secondLastname) { 20 | mNames = names; 21 | mFirstLastname = firstLastname; 22 | mSecondLastname = secondLastname; 23 | } 24 | 25 | public Student(String names, String firstLastname, String secondLastname, int idCloud) { 26 | mNames = names; 27 | mFirstLastname = firstLastname; 28 | mSecondLastname = secondLastname; 29 | mIdCloud = idCloud; 30 | } 31 | 32 | public String getNames() { 33 | return mNames; 34 | } 35 | 36 | public String getFirstLastname() { 37 | return mFirstLastname; 38 | } 39 | 40 | public String getSecondLastname() { 41 | return mSecondLastname; 42 | } 43 | 44 | public int getIdCloud() { 45 | return mIdCloud; 46 | } 47 | 48 | @Override 49 | public int compare(Student lhs, Student rhs) { 50 | return lhs.getFirstLastname().compareTo(rhs.getFirstLastname()); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | Student student = (Student)o; 56 | if (student.getFirstLastname().equals(mFirstLastname) && 57 | student.getSecondLastname().equals(mSecondLastname) && 58 | student.getNames().equals(mNames)) 59 | return true; 60 | return false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Networking/GetRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Networking; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | import java.util.ArrayList; 10 | 11 | class GetRequest extends Request { 12 | 13 | /** 14 | * Perform a GET request to {@link #API_URL}. 15 | * @param token Authorization token 16 | * @return String responses. 17 | * @throws IOException 18 | */ 19 | @Override 20 | public ArrayList perform(String token) throws IOException { 21 | URL url; 22 | HttpURLConnection urlConnection = null; 23 | 24 | ArrayList responses = new ArrayList<>(); 25 | try { 26 | url = new URL(API_URL); 27 | urlConnection = (HttpURLConnection) url.openConnection(); 28 | urlConnection.setRequestMethod(GET); 29 | urlConnection.setRequestProperty(AUTHORIZATION, TOKEN_HEADER + token); 30 | InputStream is; 31 | 32 | int responseCode = urlConnection.getResponseCode(); 33 | 34 | is = urlConnection.getInputStream(); 35 | InputStreamReader isr = new InputStreamReader(is); 36 | BufferedReader reader = new BufferedReader(isr); 37 | String response = ""; 38 | String line; 39 | while ((line = reader.readLine()) != null) { 40 | response += line + "\n"; 41 | } 42 | reader.close(); 43 | urlConnection.disconnect(); 44 | responses.add(response); 45 | return responses; 46 | } 47 | catch (Exception ex) { } 48 | finally { 49 | if (urlConnection != null) urlConnection.disconnect(); 50 | } 51 | return responses; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Networking/PostRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Networking; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStreamWriter; 10 | import java.net.HttpURLConnection; 11 | import java.net.URL; 12 | import java.util.ArrayList; 13 | 14 | class PostRequest extends Request { 15 | 16 | /** 17 | * Perform a POST request to {@link #API_URL}. 18 | * @param token Authorization token. 19 | * @return String responses. 20 | * @throws IOException 21 | */ 22 | @Override 23 | public ArrayList perform(String token) throws IOException { 24 | URL url; 25 | HttpURLConnection urlConnection = null; 26 | 27 | ArrayList responses = new ArrayList<>(); 28 | try { 29 | url = new URL(API_URL); 30 | JSONArray jsonArray = new JSONArray(""); 31 | 32 | for (int i = 0; i< jsonArray.length(); i++){ 33 | JSONObject object = jsonArray.getJSONObject(i); 34 | 35 | urlConnection = (HttpURLConnection) url.openConnection(); 36 | urlConnection.setDoOutput(true); 37 | urlConnection.setDoInput(true); 38 | urlConnection.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON); 39 | urlConnection.setRequestProperty(ACCEPT, APPLICATION_JSON); 40 | urlConnection.setRequestProperty(AUTHORIZATION, TOKEN_HEADER + token); 41 | urlConnection.setRequestMethod(POST); 42 | 43 | OutputStreamWriter wr= new OutputStreamWriter(urlConnection.getOutputStream()); 44 | wr.write(object.toString()); 45 | wr.flush(); 46 | 47 | int responsecode = urlConnection.getResponseCode(); 48 | 49 | if (responsecode==201){ 50 | BufferedReader br = new BufferedReader( 51 | new InputStreamReader((urlConnection.getInputStream()))); 52 | String output= ""; 53 | String a; 54 | while ((a = br.readLine()) != null) { 55 | output += a; 56 | } 57 | responses.add(output); 58 | } 59 | } 60 | return responses; 61 | } 62 | catch (Exception ex) { } 63 | finally { 64 | if (urlConnection != null) urlConnection.disconnect(); 65 | } 66 | return responses; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Networking/Request.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Networking; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * Clase abstracta que no puede ser instanciada y que representa un web request. 8 | * Hasta ahora tiene de subclases a {@link PostRequest} y a {@link GetRequest}. 9 | */ 10 | public abstract class Request { 11 | public static final String API_URL = "https://guasapuc.herokuapp.com/api/students"; 12 | public static final String GET = "GET"; 13 | public static final String POST = "POST"; 14 | public static final String AUTHORIZATION = "Authorization"; 15 | public static final String TOKEN_HEADER = "Token token="; 16 | public static final String CONTENT_TYPE = "Content-Type"; 17 | public static final String APPLICATION_JSON = "application/json"; 18 | public static final String ACCEPT = "Accept"; 19 | 20 | /** 21 | * Factory que crea un {@link GetRequest} o un {@link PostRequest} dependiendo del modo que 22 | * es pasado de parámetro. 23 | * @param mode Puede ser {@link #GET} o {@link #POST}. 24 | * @return Nuevo request. 25 | */ 26 | public static Request createRequest(String mode) { 27 | if (mode.equals(GET)) { 28 | return new GetRequest(); 29 | } 30 | else if (mode.equals(POST)) { 31 | return new PostRequest(); 32 | } 33 | return null; 34 | } 35 | 36 | /** 37 | * Metodo que debe ser 'overrided'. Realiza el request. 38 | * @param token Authorization token. 39 | * @return String responses. 40 | * @throws IOException 41 | */ 42 | public ArrayList perform(String token) throws IOException { 43 | return null; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Persistence/DatabaseContract.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Persistence; 2 | 3 | import android.provider.BaseColumns; 4 | 5 | /** 6 | * Contiene las constantes relacionadas con la base de datos. Nombres de tablas, nombres de columnas 7 | * , operaciones estandar como crear o eliminar tablas, etc. 8 | */ 9 | public final class DatabaseContract { 10 | 11 | public DatabaseContract() { } 12 | 13 | public static abstract class Students implements BaseColumns { 14 | 15 | public static final String TABLE_NAME = "STUDENTS"; 16 | public static final String COLUMN_NAME_STUDENT_NAMES = "names"; 17 | public static final String COLUMN_NAME_FIRST_LASTNAME = "firstlastname"; 18 | public static final String COLUMN_NAME_SECOND_LASTNAME = "secondlastname"; 19 | public static final String COLUMN_ID_CLOUD = "idcloud"; 20 | 21 | public static final String TEXT_TYPE = " TEXT"; 22 | public static final String INTEGER_TYPE = " INTEGER"; 23 | public static final String COMMA_SEP = ","; 24 | 25 | public static final String SQL_CREATE_STUDENTS_TABLE = 26 | "CREATE TABLE " + Students.TABLE_NAME + " (" + 27 | Students._ID + " INTEGER PRIMARY KEY," + 28 | Students.COLUMN_NAME_STUDENT_NAMES + TEXT_TYPE + COMMA_SEP + 29 | Students.COLUMN_NAME_FIRST_LASTNAME + TEXT_TYPE + COMMA_SEP + 30 | Students.COLUMN_NAME_SECOND_LASTNAME + TEXT_TYPE + COMMA_SEP+ 31 | Students.COLUMN_ID_CLOUD + INTEGER_TYPE + 32 | " )"; 33 | 34 | public static final String SQL_DELETE_STUDENTS = 35 | "DROP TABLE IF EXISTS " + Students.TABLE_NAME; 36 | } 37 | } 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Persistence/StudentsDbHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Persistence; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | 7 | /** 8 | * Clase que nos permite interactuar con la base de datos. 9 | * Documentacion oficial 10 | * http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html 11 | */ 12 | public class StudentsDbHelper extends SQLiteOpenHelper { 13 | public static final int DATABASE_VERSION = 2; 14 | public static final String DATABASE_NAME = "Students.db"; 15 | 16 | private static StudentsDbHelper sInstance; 17 | 18 | private StudentsDbHelper(Context context) { 19 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 20 | } 21 | 22 | /** 23 | * Nos aseguramos de que solo haya una instancia para evitar errores. 24 | * Mas detalles: 25 | * http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html 26 | */ 27 | public static synchronized StudentsDbHelper getInstance(Context context) { 28 | if (sInstance == null) { 29 | sInstance = new StudentsDbHelper(context.getApplicationContext()); 30 | } 31 | return sInstance; 32 | } 33 | 34 | @Override 35 | public void onCreate(SQLiteDatabase db) { 36 | db.execSQL(DatabaseContract.Students.SQL_CREATE_STUDENTS_TABLE); 37 | } 38 | 39 | @Override 40 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 41 | db.execSQL(DatabaseContract.Students.SQL_DELETE_STUDENTS); 42 | onCreate(db); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Provider/StudentsContract.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Provider; 2 | 3 | import android.content.ContentResolver; 4 | import android.net.Uri; 5 | import android.provider.BaseColumns; 6 | 7 | /** 8 | * Esta clase provee las constantes y URIs necesarias para trabajar con el StudentsProvider 9 | */ 10 | public final class StudentsContract { 11 | 12 | public static final String AUTHORITY = "com.example.andres.myapplication.provider"; 13 | public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY); 14 | public static final Uri STUDENTS_URI = Uri.withAppendedPath( 15 | StudentsContract.BASE_URI, "/students"); 16 | 17 | /** 18 | * MIME Types 19 | * Para listas se necesita 'vnd.android.cursor.dir/vnd.com.example.andres.provider.students' 20 | * Para items se necesita 'vnd.android.cursor.item/vnd.com.example.andres.provider.students' 21 | * La primera parte viene está definida en constantes de ContentResolver 22 | */ 23 | public static final String URI_TYPE_STUDENT_DIR = ContentResolver.CURSOR_DIR_BASE_TYPE + 24 | "/vnd.com.example.andres.provider.students"; 25 | 26 | public static final String URI_TYPE_STUDENT_ITEM = ContentResolver.CURSOR_ITEM_BASE_TYPE + 27 | "/vnd.com.example.andres.provider.students"; 28 | 29 | /** 30 | * Tabla definida en provider. Acá podría ser una distinta a la de la base de datos, 31 | * pero consideramos la misma. 32 | */ 33 | public static final class StudentsColumns implements BaseColumns { 34 | 35 | private StudentsColumns() {} 36 | 37 | public static final String NAMES = "names"; 38 | public static final String FIRST_LASTNAME = "firstlastname"; 39 | public static final String SECOND_LASTNAME = "secondlastname"; 40 | public static final String ID_CLOUD = "idcloud"; 41 | 42 | public static final String DEFAULT_SORT_ORDER = FIRST_LASTNAME + " ASC"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Provider/StudentsProvider.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Provider; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentValues; 5 | import android.content.UriMatcher; 6 | import android.database.Cursor; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.net.Uri; 9 | import android.text.TextUtils; 10 | 11 | import com.example.andres.myapplication.Persistence.DatabaseContract; 12 | import com.example.andres.myapplication.Persistence.StudentsDbHelper; 13 | 14 | /** 15 | * Clase que extiende a ContentProvider y que interactua con la base de datos. 16 | */ 17 | public class StudentsProvider extends ContentProvider { 18 | public static final int STUDENT_LIST = 1; 19 | public static final int STUDENT_ID = 2; 20 | 21 | private static final UriMatcher sUriMatcher; 22 | 23 | static{ 24 | sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 25 | /** 26 | * URI para todos los estudiantes. 27 | * Se setea que cuando se pregunta a UriMatcher por la URI: 28 | * content://com.example.andres.myapplication.provider/students 29 | * se devuelva un entero con el valor de 1. 30 | */ 31 | sUriMatcher.addURI(StudentsContract.AUTHORITY, "students", STUDENT_LIST); 32 | /** 33 | * URI para un estudiante. 34 | * Se setea que cuando se pregunta a UriMatcher por la URI: 35 | * content://com.example.andres.myapplication.provider/students/# 36 | * se devuelva un entero con el valor de 2. 37 | */ 38 | sUriMatcher.addURI(StudentsContract.AUTHORITY, "students/#", STUDENT_ID); 39 | } 40 | 41 | /** 42 | * Instancia de StudentsDbHelper para interactuar con la base de datos 43 | */ 44 | private StudentsDbHelper mDbHelper; 45 | 46 | public StudentsProvider() { } 47 | 48 | @Override 49 | public boolean onCreate() { 50 | mDbHelper = StudentsDbHelper.getInstance(getContext()); 51 | return true; 52 | } 53 | 54 | /** 55 | * Llamado para borrar una o más filas de una tabla 56 | */ 57 | @Override 58 | public int delete(Uri uri, String selection, String[] selectionArgs) { 59 | SQLiteDatabase db = mDbHelper.getWritableDatabase(); 60 | int rows = 0; 61 | switch (sUriMatcher.match(uri)) { 62 | case STUDENT_LIST: 63 | // Se borran todas las filas 64 | rows = db.delete(DatabaseContract.Students.TABLE_NAME, null, null); 65 | break; 66 | case STUDENT_ID: 67 | // Se borra la fila del ID seleccionado 68 | rows = db.delete(DatabaseContract.Students.TABLE_NAME, selection, selectionArgs); 69 | } 70 | // Se retorna el número de filas eliminadas 71 | return rows; 72 | } 73 | 74 | /** 75 | * Se determina el MIME Type del dato o conjunto de datos al que apunta la URI 76 | */ 77 | @Override 78 | public String getType(Uri uri) { 79 | switch (sUriMatcher.match(uri)){ 80 | case STUDENT_LIST: 81 | return StudentsContract.URI_TYPE_STUDENT_DIR; 82 | case STUDENT_ID: 83 | return StudentsContract.URI_TYPE_STUDENT_ITEM; 84 | default: 85 | return null; 86 | } 87 | } 88 | 89 | /** 90 | * Inserta nuevo estudiante 91 | */ 92 | @Override 93 | public Uri insert(Uri uri, ContentValues values) { 94 | SQLiteDatabase db = mDbHelper.getWritableDatabase(); 95 | db.insert(DatabaseContract.Students.TABLE_NAME, null, values); 96 | // Le avisa a los observadores 97 | getContext().getContentResolver().notifyChange(uri, null); 98 | return null; 99 | } 100 | 101 | /** 102 | * Retorna el o los datos que se le pida de acuerdo a la URI 103 | */ 104 | @Override 105 | public Cursor query(Uri uri, String[] projection, String selection, 106 | String[] selectionArgs, String sortOrder) { 107 | SQLiteDatabase db = mDbHelper.getReadableDatabase(); 108 | switch (sUriMatcher.match(uri)){ 109 | // Se pide la lista completa de estudiantes 110 | case STUDENT_LIST: 111 | // Si no hay un orden especificado, 112 | // lo ordenamos de manera ascendente de acuerdo a lo que diga el contrato 113 | if (sortOrder == null || TextUtils.isEmpty(sortOrder)) 114 | sortOrder = StudentsContract.StudentsColumns.DEFAULT_SORT_ORDER; 115 | break; 116 | // Se pide un estudiante en particular 117 | case STUDENT_ID: 118 | // Se adjunta la ID del estudiante selecciondo en el filtro de la seleccion 119 | if (selection == null) 120 | selection = ""; 121 | selection = selection + "_ID = " + uri.getLastPathSegment(); 122 | break; 123 | // La URI que se recibe no está definida 124 | default: 125 | throw new IllegalArgumentException( 126 | "Unsupported URI: " + uri); 127 | } 128 | 129 | Cursor cursor = db.query(DatabaseContract.Students.TABLE_NAME, 130 | projection, 131 | selection, 132 | selectionArgs, 133 | null, 134 | null, 135 | sortOrder); 136 | // Se retorna un cursor sobre el cual se debe iterar para obtener los datos 137 | return cursor; 138 | } 139 | 140 | @Override 141 | public int update(Uri uri, ContentValues values, String selection, 142 | String[] selectionArgs) { 143 | // No se implemento un update 144 | throw new UnsupportedOperationException("Not yet implemented"); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Services/AuthenticatorService.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Services; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | import com.example.andres.myapplication.Authenticator.AccountAuthenticator; 8 | 9 | /** 10 | * Este servicio permite a otros procesos enlazarse (bind) a el y permite que se puedan comunicar 11 | * con {@link AccountAuthenticator}. Lo único que se debe hacer es llamar al método 12 | * {@link AccountAuthenticator#getIBinder()}. 13 | */ 14 | public class AuthenticatorService extends Service { 15 | private AccountAuthenticator mAuthenticator; 16 | 17 | @Override 18 | public void onCreate() { 19 | // Create a new authenticator object 20 | mAuthenticator = new AccountAuthenticator(this); 21 | } 22 | 23 | @Override 24 | public IBinder onBind(Intent intent) { 25 | return mAuthenticator.getIBinder(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/Services/SyncService.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.Services; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.util.Log; 7 | 8 | import com.example.andres.myapplication.SyncAdapter.SyncAdapter; 9 | 10 | /** 11 | * Permite al framework sync adapter correr el código de {@link SyncAdapter}. 12 | */ 13 | public class SyncService extends Service { 14 | 15 | private static final Object sSyncAdapterLock = new Object(); 16 | private static SyncAdapter sSyncAdapter = null; 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | synchronized (sSyncAdapterLock) { 22 | if (sSyncAdapter == null) { 23 | sSyncAdapter = new SyncAdapter(getApplicationContext(), true); 24 | } 25 | } 26 | } 27 | 28 | /** 29 | * Nuevos sync requests va a ser mandados directamente al SyncAdapter usando este canal. 30 | * @param intent Intent que invoca. 31 | * @return Binder handle for {@link SyncAdapter} 32 | */ 33 | @Override 34 | public IBinder onBind(Intent intent) { 35 | return sSyncAdapter.getSyncAdapterBinder(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/StudentAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.TextView; 9 | 10 | import com.example.andres.myapplication.Model.Item; 11 | 12 | import java.util.ArrayList; 13 | 14 | /** 15 | * Genera la vista de cada celda de la lista de estudiantes. 16 | */ 17 | public class StudentAdapter extends ArrayAdapter 18 | { 19 | private LayoutInflater mLayoutInflater; 20 | 21 | public StudentAdapter(Context context, int resource, ArrayList objects){ 22 | super(context, resource, objects); 23 | mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 24 | } 25 | 26 | @Override 27 | public View getView(int position, View convertView, ViewGroup parent) { 28 | View view = convertView; 29 | Item item = getItem(position); 30 | if (view == null){ 31 | // Crear nueva celda 32 | // Si es una celda de letra entonces tiene layout diferente. 33 | if (item.getCellType() == Item.LETTER_CELL) { 34 | view = mLayoutInflater.inflate(R.layout.list_item_2, parent, false); 35 | } 36 | else { 37 | view = mLayoutInflater.inflate(R.layout.list_item, parent, false); 38 | } 39 | } 40 | TextView textView = (TextView) view.findViewById(R.id.text_view); 41 | textView.setText(item.getText()); 42 | return view; 43 | } 44 | 45 | /** 46 | * Numero de tipos de items. 47 | */ 48 | @Override 49 | public int getViewTypeCount() { 50 | return 2; 51 | } 52 | 53 | /** 54 | * Retorna tipo de item de position 55 | */ 56 | @Override 57 | public int getItemViewType(int position) { 58 | Item item = getItem(position); 59 | return item.getCellType(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/andres/myapplication/SyncAdapter/SyncAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.andres.myapplication.SyncAdapter; 2 | 3 | import android.accounts.Account; 4 | import android.accounts.AccountManager; 5 | import android.content.AbstractThreadedSyncAdapter; 6 | import android.content.ContentProviderClient; 7 | import android.content.ContentResolver; 8 | import android.content.ContentValues; 9 | import android.content.Context; 10 | import android.content.OperationApplicationException; 11 | import android.content.SyncResult; 12 | import android.database.Cursor; 13 | import android.net.Uri; 14 | import android.os.Bundle; 15 | import android.os.RemoteException; 16 | 17 | import com.example.andres.myapplication.Authenticator.AccountAuthenticator; 18 | import com.example.andres.myapplication.Model.Student; 19 | import com.example.andres.myapplication.Networking.Request; 20 | import com.example.andres.myapplication.Provider.StudentsContract; 21 | 22 | import org.json.JSONArray; 23 | import org.json.JSONException; 24 | import org.json.JSONObject; 25 | 26 | import java.io.BufferedReader; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.InputStreamReader; 30 | import java.io.OutputStreamWriter; 31 | import java.net.HttpURLConnection; 32 | import java.net.URL; 33 | import java.util.ArrayList; 34 | 35 | /** 36 | * Clase SyncAdapter que implementa los métodos relevantes para la sincronzación. 37 | * El método {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)} 38 | * es el más relevante, ya que este es el que se llama cuando se va a realizar la sincronización. 39 | */ 40 | public class SyncAdapter extends AbstractThreadedSyncAdapter { 41 | 42 | private ContentResolver mContentResolver; 43 | private String mToken; 44 | private AccountManager mAccountManager; 45 | 46 | public SyncAdapter (Context context, boolean autoInitialize){ 47 | super(context, autoInitialize); 48 | mContentResolver = context.getContentResolver(); 49 | mAccountManager = AccountManager.get(context); 50 | } 51 | 52 | /** 53 | * Llamado cuando se sincroniza. Acá se debe hacer el llamado al servidor y la 54 | * actualizacion de datos locales. 55 | * @param account 56 | * @param extras 57 | * @param authority 58 | * @param provider 59 | * @param syncResult 60 | */ 61 | @Override 62 | public void onPerformSync(Account account, Bundle extras, String authority, 63 | ContentProviderClient provider, SyncResult syncResult) { 64 | try { 65 | // No importa que el thread se bloquee ya que es asincrónico. 66 | mToken = mAccountManager.blockingGetAuthToken(account, 67 | AccountAuthenticator.ACCOUNT_TYPE, true); 68 | 69 | Request request = Request.createRequest(Request.GET); 70 | ArrayList results = request.perform(mToken); 71 | 72 | if (results.size() == 1){ 73 | updateData(results); 74 | } 75 | // Manejo de errores 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | 81 | /** 82 | * Processes data coming from the cloud, POST or GET, and upgrade the corresponding values 83 | * calling corresponding methods. 84 | */ 85 | protected void updateData(ArrayList responses) 86 | throws JSONException, RemoteException, OperationApplicationException { 87 | if (responses == null) return; 88 | if (responses.size()>0){ 89 | String response = responses.get(0); 90 | JSONArray jsonArray = new JSONArray(response); 91 | Uri uri = StudentsContract.STUDENTS_URI; 92 | Cursor cursor = mContentResolver.query(StudentsContract.STUDENTS_URI, 93 | null, null, null, null); 94 | ArrayList students = new ArrayList<>(); 95 | cursor.moveToFirst(); 96 | while (cursor.moveToNext()) { 97 | String names = cursor.getString(cursor.getColumnIndexOrThrow( 98 | StudentsContract.StudentsColumns.NAMES)); 99 | String firstLast = cursor.getString(cursor.getColumnIndexOrThrow( 100 | StudentsContract.StudentsColumns.FIRST_LASTNAME)); 101 | String secondLast = cursor.getString(cursor.getColumnIndexOrThrow( 102 | StudentsContract.StudentsColumns.SECOND_LASTNAME)); 103 | students.add(firstLast + " " + secondLast + " " + names); 104 | } 105 | for (int i=0; i< jsonArray.length(); i++) { 106 | JSONObject jsonObject = jsonArray.getJSONObject(i); 107 | String name = jsonObject.getString(Student.NAME); 108 | String firstLastname = jsonObject.getString(Student.FIRST_LASTNAME); 109 | String secondLastname = jsonObject.getString(Student.SECOND_LASTNAME); 110 | int idCloud = jsonObject.getInt(Student.ID); 111 | if (!students.contains(firstLastname + " " + secondLastname + " " + name)){ 112 | ContentValues values = new ContentValues(); 113 | values.put(StudentsContract.StudentsColumns.NAMES, name); 114 | values.put(StudentsContract.StudentsColumns.FIRST_LASTNAME, firstLastname); 115 | values.put(StudentsContract.StudentsColumns.SECOND_LASTNAME, secondLastname); 116 | values.put(StudentsContract.StudentsColumns.ID_CLOUD, idCloud); 117 | mContentResolver.insert(uri , values); 118 | } 119 | } 120 | cursor.close(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_action_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aamatte/tutorial-sync-adapter/c60f4976ed460fd090ed15d9843085b94044be65/app/src/main/res/drawable/button_action_bar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aamatte/tutorial-sync-adapter/c60f4976ed460fd090ed15d9843085b94044be65/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sync_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aamatte/tutorial-sync-adapter/c60f4976ed460fd090ed15d9843085b94044be65/app/src/main/res/drawable/sync_button.png -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_authenticator.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | 29 | 30 | 36 | 37 | 43 | 44 |