Android
Composants d'architecture Android
Recherche…
Introduction
Android Architecture Components est une nouvelle collection de bibliothèques qui vous permet de concevoir des applications robustes, testables et maintenables. Les parties principales sont: les cycles de vie, ViewModel, LiveData, Room.
Ajouter des composants d'architecture
Projet build.gradle
allprojects {
repositories {
jcenter()
// Add this if you use Gradle 4.0+
google()
// Add this if you use Gradle < 4.0
maven { url 'https://maven.google.com' }
}
}
ext {
archVersion = '1.0.0-alpha5'
}
Application build gradle
// For Lifecycles, LiveData, and ViewModel
compile "android.arch.lifecycle:runtime:$archVersion"
compile "android.arch.lifecycle:extensions:$archVersion"
annotationProcessor "android.arch.lifecycle:compiler:$archVersion"
// For Room
compile "android.arch.persistence.room:runtime:$archVersion"
annotationProcessor "android.arch.persistence.room:compiler:$archVersion"
// For testing Room migrations
testCompile "android.arch.persistence.room:testing:$archVersion"
// For Room RxJava support
compile "android.arch.persistence.room:rxjava2:$archVersion"
Utilisation du cycle de vie dans AppCompatActivity
Prolongez votre activité de cette activité
public abstract class BaseCompatLifecycleActivity extends AppCompatActivity implements LifecycleRegistryOwner {
// We need this class, because LifecycleActivity extends FragmentActivity not AppCompatActivity
@NonNull
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@NonNull
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}
ViewModel avec transformations LiveData
public class BaseViewModel extends ViewModel {
private static final int TAG_SEGMENT_INDEX = 2;
private static final int VIDEOS_LIMIT = 100;
// We save input params here
private final MutableLiveData<Pair<String, String>> urlWithReferrerLiveData = new MutableLiveData<>();
// transform specific uri param to "tag"
private final LiveData<String> currentTagLiveData = Transformations.map(urlWithReferrerLiveData, pair -> {
Uri uri = Uri.parse(pair.first);
List<String> segments = uri.getPathSegments();
if (segments.size() > TAG_SEGMENT_INDEX)
return segments.get(TAG_SEGMENT_INDEX);
return null;
});
// transform "tag" to videos list
private final LiveData<List<VideoItem>> videoByTagData = Transformations.switchMap(currentTagLiveData, tag -> contentRepository.getVideoByTag(tag, VIDEOS_LIMIT));
ContentRepository contentRepository;
public BaseViewModel() {
// some inits
}
public void setUrlWithReferrer(String url, String referrer) {
// set value activates observers and transformations
urlWithReferrerLiveData.setValue(new Pair<>(url, referrer));
}
public LiveData<List<VideoItem>> getVideoByTagData() {
return videoByTagData;
}
}
Quelque part dans l'interface utilisateur:
public class VideoActivity extends BaseCompatLifecycleActivity {
private VideoViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get ViewModel
viewModel = ViewModelProviders.of(this).get(BaseViewModel.class);
// Add observer
viewModel.getVideoByTagData().observe(this, data -> {
// some checks
adapter.updateData(data);
});
...
if (savedInstanceState == null) {
// init loading only at first creation
// you just set params and
viewModel.setUrlWithReferrer(url, referrer);
}
}
La pérennité de la chambre
Room requiert quatre parties: Classe de base de données, classes DAO, classes d'entité et classes de migration (vous ne pouvez désormais utiliser que les méthodes DDL ):
Classes d'entités
// Set custom table name, add indexes
@Entity(tableName = "videos",
indices = {@Index("title")}
)
public final class VideoItem {
@PrimaryKey // required
public long articleId;
public String title;
public String url;
}
// Use ForeignKey for setup table relation
@Entity(tableName = "tags",
indices = {@Index("score"), @Index("videoId"), @Index("value")},
foreignKeys = @ForeignKey(entity = VideoItem.class,
parentColumns = "articleId",
childColumns = "videoId",
onDelete = ForeignKey.CASCADE)
)
public final class VideoTag {
@PrimaryKey
public long id;
public long videoId;
public String displayName;
public String value;
public double score;
}
Classes DAO
@Dao
public interface VideoDao {
// Create insert with custom conflict strategy
@Insert(onConflict = OnConflictStrategy.REPLACE)
void saveVideos(List<VideoItem> videos);
// Simple update
@Update
void updateVideos(VideoItem... videos);
@Query("DELETE FROM tags WHERE videoId = :videoId")
void deleteTagsByVideoId(long videoId);
// Custom query, you may use select/delete here
@Query("SELECT v.* FROM tags t LEFT JOIN videos v ON v.articleId = t.videoId WHERE t.value = :tag ORDER BY updatedAt DESC LIMIT :limit")
LiveData<List<VideoItem>> getVideosByTag(String tag, int limit);
}
Classe de base de données
// register your entities and DAOs
@Database(entities = {VideoItem.class, VideoTag.class}, version = 2)
public abstract class ContentDatabase extends RoomDatabase {
public abstract VideoDao videoDao();
}
Migrations
public final class Migrations {
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
final String[] sqlQueries = {
"CREATE TABLE IF NOT EXISTS `tags` (`id` INTEGER PRIMARY KEY AUTOINCREMENT," +
" `videoId` INTEGER, `displayName` TEXT, `value` TEXT, `score` REAL," +
" FOREIGN KEY(`videoId`) REFERENCES `videos`(`articleId`)" +
" ON UPDATE NO ACTION ON DELETE CASCADE )",
"CREATE INDEX `index_tags_score` ON `tags` (`score`)",
"CREATE INDEX `index_tags_videoId` ON `tags` (`videoId`)"};
for (String query : sqlQueries) {
database.execSQL(query);
}
}
};
public static final Migration[] ALL = {MIGRATION_1_2};
private Migrations() {
}
}
Utiliser dans la classe Application ou fournir via Dagger
ContentDatabase provideContentDatabase() {
return Room.databaseBuilder(context, ContentDatabase.class, "data.db")
.addMigrations(Migrations.ALL).build();
}
Ecrivez votre référentiel:
public final class ContentRepository {
private final ContentDatabase db;
private final VideoDao videoDao;
public ContentRepository(ContentDatabase contentDatabase, VideoDao videoDao) {
this.db = contentDatabase;
this.videoDao = videoDao;
}
public LiveData<List<VideoItem>> getVideoByTag(@Nullable String tag, int limit) {
// you may fetch from network, save to database
....
return videoDao.getVideosByTag(tag, limit);
}
}
Utiliser dans ViewModel:
ContentRepository contentRepository = ...;
contentRepository.getVideoByTag(tag, limit);
LiveData personnalisé
Vous pouvez écrire des données LiveData personnalisées si vous avez besoin d'une logique personnalisée.
N'écrivez pas de classe personnalisée si vous avez seulement besoin de transformer des données (utilisez la classe Transformations)
public class LocationLiveData extends LiveData<Location> {
private LocationManager locationManager;
private LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Do something
}
@Override
public void onProviderEnabled(String provider) {
// Do something
}
@Override
public void onProviderDisabled(String provider) {
// Do something
}
};
public LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
// We have observers, start working
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
// We have no observers, stop working
locationManager.removeUpdates(listener);
}
}
Composant personnalisé prenant en compte le cycle de vie
Le cycle de vie de chaque composant de l'interface utilisateur a changé comme indiqué sur l'image. Vous pouvez créer un composant qui sera notifié du changement d'état du cycle de vie:
public class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
private Lifecycle lifecycle;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}