Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How Sky Kids Team Work

César
March 16, 2016

How Sky Kids Team Work

This is a talk I gave on March 2016 about how Sky Kids used to work as a team during my period collaborating on the Android App.

César

March 16, 2016
Tweet

More Decks by César

Other Decks in Programming

Transcript

  1. 1) The Team 2) How we work 3) The app

    and its insides 4) Demo Agenda:
  2. Fred Rogers “There are three ways to ultimate success: The

    first is to be kind. The second way is to be kind. The third way is to be kind.” http://goo.gl/h9xZhl Google confirmation:
  3. +

  4. TYPICAL FEATURE TEAM STRUCTURE Android Developer #1 Android Developer #2

    Feature Team Tester #1 iOS Developer #1 iOS Developer #2 Tester #2 Visual Designer User Experience Agile Coach/SM Assistant Product Owner Android Developer #1 Android Developer #2 Feature Team 2 Tester #1 iOS Developer #1 iOS Developer #2 Tester #2 Visual Designer User Experience Agile Coach/SM Assistant Product Owner 2 1 Android Developer #1 Android Developer #2 Feature Team # Tester #1 iOS Developer #1 iOS Developer #2 Tester #2 Visual Designer User Experience Agile Coach/SM Assistant Product Owner 7 +/-2 iOS Android Tester
  5. Kanban vs Scrum There are many valuable aspects to both

    Scrum and Kanban, that have worked well for us. We stopped using sprint terminology. However… We use Zenhub kanban board, with workflow state columns and explicit WIP limits. We continue with a 2 week development cadence ending with our regular ceremonies: demo, retros and planning for what is ahead, as well as fortnightly backlog grooming.
  6. Approach Some concepts of Clean arquitecture involve: - Independency of

    frameworks - Testability - Independency of UI - Independency of Database - Independency of any external agency S.O.L.I.D principles, cohesion and bla bla bla
  7. @NonNull 
 public HomePresenter getHomePresenter(
 @NonNull final HomePresenter.View homeView, 


    @NonNull final Persona persona
 ) {
 return new HomePresenter(
 homeView,
 persona,
 accountModule.getAccountManager(),
 storageModule.getChannelRepository(),
 storageModule.getPersonaRepository(),
 storageModule.getCatFeedsRepository(),
 serviceModule.getAnalyticsService(),
 serviceModule.getSoundEffects()
 );
 } Depencency Injection
  8. Rx in our Views @NonNull @Override public Observable<Void> onCloseClicked() {


    return showVideoBackgroundView.onCloseClicked();
 } @NonNull @Override public Observable<Episode> onEpisodeClicked() {
 return seriesListView.onEpisodeClicked();
 } public interface View {
 
 @NonNull Observable<Episode> onEpisodeClicked();
 @NonNull Observable<Void> onCloseClicked();
 
 boolean hasVideoItem();
 
 void goToSignIn();
 void finish();
 void setBackgroundImageUrl(@Nullable final String backgroundUrl);
 void setChannelLogoUrl(@Nullable final String channelLogoUrl);
 void showDefaultEpisodeLoading(final boolean loading);
 void setVideoItem(@NonNull VideoItem videoItem, boolean play);
 void setAvatar(@NonNull final Avatar avatar);
 }
  9. Rx in our Presenters public Observable<Void> startup() {
 return initSps()


    .switchMap(ignored -> {
 if (!accountManager.isSignedIn()) {
 return Observable.error(new UserNotSignedInException());
 }
 return Observable.just(null);
 })
 .switchMap(ignored -> spsService.loadUserDetails())
 .switchMap(userDetails -> {
 if (!userDetails.hasVarietyEntitlement()) {
 return Observable.error(new UserNotEntitledException());
 }
 return Observable.just(userDetails);
 })
 .doOnNext(userDetails -> {
 analytics.initialiseWithUserIdentifier(userDetails.getOmnitureKey());
 downloadsRepository.saveDownloadEntitlement(userDetails.hasSkyGoExtra());
 })
 .switchMap(this::loadChannels)
 .doOnError(this::handleError)
 .map(ignored -> null);
 }
  10. Rx in our Repositories /**
 * Return an observable that

    emits a single Episode for a show url.
 * @param showUrl show url.
 * @return an observable that emits a single Episode or error.
 */
 public Observable<Episode> getDefaultEpisodeForShow(@NonNull String showUrl) {
 return defaultEpisodeCache.get(showUrl, () ->
 getSeriesForShow(showUrl)
 .flatMap(Observable::from)
 .last()
 .flatMap(Series::getEpisodes)
 .flatMap(Observable::from)
 .first()
 .subscribeOn(Schedulers.io())
 .observeOn(observeOn));
 }
  11. Rx in our Services @NonNull @Override public Observable<List<Show>> getShowsFromChannelUrl(String url)

    {
 return catFeedsService.getNode(new CatFeedRequest.Builder().url(url).build())
 .toObservable()
 .flatMap(node -> node.getNodesByRelation(Node.Relation.CHILD_PAGE_NODE))
 .flatMap(node ->
 catFeedsService.getNode(CatFeedRequest.Builder.catFeedRequest()
 .node(node)
 .expansion(ITEM_SUMMARY_EXPANSION.toString())
 .build()).toObservable())
 .flatMap(node ->
 node.getNodesByRelation(ITEM_GROUP_NODE))
 .map(Node::getItemGroup)
 .flatMap(node ->
 node.getNodesByRelation(ITEM_SUMMARY_NODE))
 .map(Node::getItemSummary)
 .map(this::nodeToShow)
 .toList()
 .doOnSubscribe(() -> time = System.currentTimeMillis());
 }