Android
Componenti di architettura Android
Ricerca…
introduzione
Android Architecture Components è una nuova raccolta di librerie che ti aiuta a progettare app robuste, testabili e manutenibili. Parti principali sono: Cicli di vita, ViewModel, LiveData, Room.
Aggiungi componenti di architettura
Progetto 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'
}
Gradle di applicazione
// 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"
Utilizzo del ciclo di vita in AppCompatActivity
Estendi la tua attività da questa attività
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 con trasformazioni 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;
}
}
Da qualche parte nell'interfaccia utente:
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);
}
}
Peristence della stanza
Le room richiedono quattro parti: classe Database, classi DAO, classi entità e classi di migrazione (ora puoi utilizzare solo i metodi DDL ):
Classi di entità
// 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;
}
Lezioni 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 di database
// register your entities and DAOs
@Database(entities = {VideoItem.class, VideoTag.class}, version = 2)
public abstract class ContentDatabase extends RoomDatabase {
public abstract VideoDao videoDao();
}
migrazioni
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() {
}
}
Utilizzare in classe Application o fornire tramite Dagger
ContentDatabase provideContentDatabase() {
return Room.databaseBuilder(context, ContentDatabase.class, "data.db")
.addMigrations(Migrations.ALL).build();
}
Scrivi il tuo repository:
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);
}
}
Usa in ViewModel:
ContentRepository contentRepository = ...;
contentRepository.getVideoByTag(tag, limit);
LiveData personalizzato
Puoi scrivere LiveData personalizzato, se hai bisogno di una logica personalizzata.
Non scrivere classi personalizzate, se hai solo bisogno di trasformare i dati (usa 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);
}
}
Componente che riconosce il ciclo di vita personalizzato
Ogni ciclo di vita dei componenti dell'interfaccia utente è cambiato come mostrato nell'immagine. È possibile creare un componente, che verrà informato sulla modifica dello stato del ciclo di vita:
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
}
}