Creating Apple Watch Apps: App, Glance and Notifications

Publicado el 05/3/2015 por Eduardo González, Mobile Apps Lead

Last Saturday we held another theEvnt workshop at Tuenti Offices. Víctor Corugedo, César Estébanez and Eduardo González (Tuenti iOS Team members) gave a complete workshop about WatchKit SDK and how to develop Apple Watch Apps.

Starting with a brief review about the Apple Watch and the internal device specifications, they continued presenting the Apple Watch development kit, the UI components already available to create apps and the internal framework architecture.

During the second part of the event, they configured a WatchKit project from scratch, dealing with common problems a developer will face once he decides to create a Watch app. Finally, they presented a toy project they were working on and explained how to create an Apple Watch App, a Glance and how to create custom interface notifications supported by the Watch.

If you could not attend for any reason, you can always check the slides and the project they used on the workshop.

We never stop and we are already preparing the next workshop. As usual, you can check all the updates in the workshops web, and follow theEvnt or the Tuenti Engineering accounts on Twitter.

#HMU25 sees integration and improvements to the Tuenti app

Publicado el 27/2/2015 por Engineering Outreach Committee

The 25th edition of Hack Me Up has just come to an end and we couldn’t be prouder of our team! After 24 hours of non-stop programming, this edition of Tuenti’s internal programming contest came to an end with two winning projects, one in the Product category, and another in the Geek category.

Yesterday (Thursday) at 17:00 saw the start of the session, which included movies, popcorn, dinner, table football, video games and music, along with code, lots of code!

As with each HMU, the winning projects were chosen by voting amongst all Tuenti employees. The winning project in the Product category was Tuenti Car, by Alexis S., Antonio A. y Víctor P., a project that presents a new user interface and UX to be able to use Tuenti in a car. 

David P. and David S. lifted the winner’s trophy in the Geek category with DeMemory, a project where memory matters.



Congratulations to both teams! As of today, they’ll join our Hall of Fame in the Tuenti office ;)

Tuenti for Android TV

Publicado el 04/2/2015 por Pedro Vicente Gómez Sánchez, Senior Software Engineer

Less than a year ago, in 2014, Google announced the Google IO event, a really important milestone for the Android Developers Community. Android TV was introduced to the world, a new adaptation of the Android OS based on Android Lollipop for televisions which some manufacturers like Samsung or LG are already using as the OS for their televisions, and which Google is already using as the OS inside its new Nexus Player.


The Tuenti team tries to be always updated and continues learning about new frameworks or technologies. This is the reason why we are going to review some of the most important features of Android TV and provide you with some examples about how to create an awesome application using Android TV features. To provide the best examples we can, we have published a public repository on Github you can fork and use to review some of the concepts explained in this blog post. Every Github star is welcome. In this repository you are going to find all the code needed to create a basic Android TV application. Take into account that all the information shown in this repository is mocked, we just wanted to show how to work over the Android TV UI layer and not how to implement the full application. Here you have a video with the final result:

The first approach to this project was not related to the code, but to the user experience and the user interface. To help me with this, Luis Javier Á., Emanuela M., Juan Manuel J. and Carmen L. came to rescue me and designed the product you can see in the previous video. The key of this development was the “focus” concept. The whole user experience and the user interface are related to this concept because the Android TV devices don’t support touchscreens. Trying to avoid soft keyboard usage and how to use the television background to provide more context to the user without distracting them were also two interesting challenges.


You can review how these ideas are actually present during the login process, where the account used to log in is shown applying a scale up effect when the view gets the focus. The “enter password” user interface has been implemented to use the Nexus Player remote instead of the operative system soft keyboard, in order to improve how the user interacts with the application and to avoid the usage of a small soft keyboard.


Regarding the application development, in this blog post we will review some of the most important details for the Android TV applications development:

  • Configure your project.
  • Create a browse view.
  • Create a detail view.
  • Create a search view.
  • Without Leanback library.

Configure your project:

To be able to create an application for Android TV you’ll have to provide one important dependency, Leanback Android TV library, which depends on V7 App Compat library and V7 RecyclerView library. These dependencies have to be added to your build.gradle file or your pom.xml, depending on the build tool used in your project. Here you have a sample to show how to add these dependencies to a project using Gradle:

dependencies{
    compile 'com.android.support:leanback-v17:21.0.3'
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.android.support:recyclerview-v7:21.0.3'
}

Once you have your dependencies added to the project, you’ll have to configure one of your activities as the Activity used by the operative system when your application starts, that is to say your launch activity. Using this intent filter you application will be ready to be launched. Here you have a sample that shows how to configure an activity to make it work as the launcher activity for an Android TV project:


 
      
        

        
      
    

Create a browse view:

Browse views are one of the most important views you can use to show content in your application. BrowseFragment is a fragment for creating Leanback browse screens. It is composed of a RowsFragment and a HeadersFragment. A BrowseFragment renders the elements of its object adapter as a set of rows in a vertical list. The elements in this adapter must be subclasses of Row.


To use this Fragment inside your application you can create your own Fragment extending from Leanback BrowseFragment or add an instance of BrowseFragment to your Activity layout and get a reference using Android Fragment Manager. We have created our own Fragment, extending from Leanback BrowseFragment and configuring it to show information related to contacts inside the user interface. To do this, we have used some important methods like: setBrandColor, setSearchAffordanceColor or setBadgeDrawable.

  @Override public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setBrandColor(getResources().getColor(R.color.primary_color));
    setSearchAffordanceColor(getResources().getColor(R.color.primary_color));
    setBadgeDrawable(getResources().getDrawable(R.drawable.icn_wink));
  }

To show information inside the BrowseFragment you’ll have to use an ArrayObjectAdapter. This Android class is thought to work like a bidimensional matrix where every row is another ArrayObjectAdapter and HeaderItem objects are used as the title for every row. The information shown is rendered using a Presenter instance. In this project you can see different Presenter implementations like ImagePresenter -created to show a simple image without any other visual effect- or IconPresenter -created to show preferences inside MainFragment-. ArrayObjectAdapter class is provided by Leanback Android library.

   @Override public void showMainInformation(List favorites, List conversations, List contacts, List mediaElements, List preferences) {
    ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
    CardPresenter bigCardPresenter = new CardPresenter(CARD_WIDTH_IN_DP, CARD_HEIGHT_IN_DP);
    CardPresenter smallCarPresenter = new CardPresenter();

    addCardInfoElementsToRowsAdapter(R.string.favorites_item_title, favorites, rowsAdapter,
        smallCarPresenter, FAVORITES_ROW);
    addCardInfoElementsToRowsAdapter(R.string.recent_conversation_item_title, conversations,
        rowsAdapter, bigCardPresenter, CONVERSATIONS_ROW);
    addCardInfoElementsToRowsAdapter(R.string.contacts_item_title, contacts, rowsAdapter,
        smallCarPresenter, CONTACTS_ROW);
    addImageInfoElementsToRowAdapter(R.string.media_elements_item_title, mediaElements, rowsAdapter,
        new ImagePresenter(), MEDIA_ROW);
    addIconInfoElementsToRowAdapter(getResources().getString(R.string.preferences), preferences,
        rowsAdapter, new IconPresenter(), PREFERENCES_ROW);

    setAdapter(rowsAdapter);
  }
   private void addIconInfoElementsToRowAdapter(String title, List preferences,ArrayObjectAdapter rowsAdapter, Presenter presenter, int id) {
    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenter);
    for (IconInfo iconInfo : preferences) {
      listRowAdapter.add(iconInfo);
    }
    rowsAdapter.add(new ListRow(new HeaderItem(id, title, ""), listRowAdapter));
  }

Configuring events listeners is also important to be able to react to user movements. For this sample we have configured onItemViewClickedListener and onItemViewSelectedListener.

 private void configureListeners() {

    setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
      @Override public void onItemSelected(Presenter.ViewHolder viewHolder, Object item, RowPresenter.ViewHolder viewHolder1, Row row) {
        if (row.getId() < MEDIA_ROW) {
          presenter.onCardInfoSelected((CardInfo) item);
        } else if (row.getId() == MEDIA_ROW) {
          presenter.onImageInfoSelected((ImageInfo) item);
        } else if (row.getId() == PREFERENCES_ROW) {
          presenter.onPreferencesSelected();
        }
      }
    });

    setOnItemViewClickedListener(new OnItemViewClickedListener() {
      @Override public void onItemClicked(Presenter.ViewHolder viewHolder, Object item,
          RowPresenter.ViewHolder viewHolder1, Row row) {
        if (row.getId() == PREFERENCES_ROW) {
          int id = ((IconInfo) item).getIconId();
          switch (id) {
            case R.drawable.icn_settings_log_out:
              presenter.logout();
              break;
            default:
          }
        } else if (row.getId() == MEDIA_ROW) {
          presenter.onImageInfoClicked((ImageInfo) item);
        } else if (row.getId() < MEDIA_ROW) {
          presenter.onCardInfoClicked((CardInfo) item);
        }
      }
    });
  }

Create a detail view:

The detailed view has been one of the key elements of the user interface for this project. We need to show detailed information about user’s contacts and some actions related to the content.


To be able to show detailed information we have created a custom Fragment extending from Leanback DetailsFragment with the same strategy we have used to create our BrowseFragment extension. To show information inside this fragment you can also use an ArrayObjectAdapter and different presenters as we have already done with the previous fragment. However, to show the detailed view you have to create a DetailsOverviewRow and a ClassPresenterSelector used to fill the ArrayObjectAdapter used inside the DetailsFragment. You can add more information to the ArrayObjectAdapter used and show a row full of data like the one shown in the previous sample.

private void showDetailInformation(Bitmap bitmap, CardInfo cardInfo){
        DetailsOverviewRow detailRow = configureDetailsOverviewRow(cardInfo, bitmap);
        ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
        DetailsOverviewRowPresenter dorPresenter =
            new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
        dorPresenter.setBackgroundColor(getResources().getColor(R.color.primary_color));
        dorPresenter.setStyleLarge(false);
        presenterSelector.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
        presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
        adapter = new ArrayObjectAdapter(presenterSelector);
        adapter.add(detailRow);
        setAdapter(adapter);
  }

To show actions inside the detail view you can add Action objects to your DetailsOverviewRow.

  private DetailsOverviewRow configureDetailsOverviewRow(CardInfo cardInfo, Bitmap bitmap) {
    final DetailsOverviewRow row = new DetailsOverviewRow(cardInfo);
    row.setImageBitmap(getActivity(), bitmap);
    row.addAction(new Action(VD_CALL_ACTION_ID, getString(R.string.vd_call_action_title)));
    row.addAction(new Action(CALL_ACTION_ID, getString(R.string.call_action_title)));
    row.addAction(new Action(CHAT_ACTION_ID, getString(R.string.chat_action_title)));
    row.addAction(new Action(EDIT_ACTION_ID, getString(R.string.edit_action_title)));
    return row;
  }

Another important element to use in your detail views is the background. Remember that creating your custom Picasso Target and using Picasso as the main image management library you can perform background changes easily.

 private void configureBackground() {
    metrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
    BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
    backgroundManager.attach(getActivity().getWindow());
    backgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
  }
  
  @Override public void showBackground(String backgroundUrl) {
    Picasso.with(getActivity())
        .load(backgroundUrl)
        .resize(metrics.widthPixels, metrics.heightPixels)
        .error(R.drawable.fragment_default_background)
        .centerCrop()
        .into(backgroundTarget);
  }

Create a search view:

In order to show a user interface that performs searches in your application using a widget based on Leanback library, you’ll have to follow an approach similar to the previous Fragment usage. Create your own Fragment and extend it from Leanback SearchFragment.


To be able to react when the user updates the search edit text you’ll have to provide a SearchResultProvider implementation and implement two methods: onQueryTextChange and onQueryTextSubmit. Implementing this method you’ll be able to perform your custom searches using a String search term.

  @Override public boolean onQueryTextChange(String newQuery) {
    Log.d(TAG, String.format("Search Query Text Change %s", newQuery));
    queryByWords(newQuery);
    return true;
  }

  @Override public boolean onQueryTextSubmit(String query) {
    Log.d(TAG, String.format("Search Query Text Submit %s", query));
    queryByWords(query);
    return true;
  }

As in the previous sample, how we can show information inside this Fragment is based on the usage of one ArrayObjectAdapter object, HeaderItems and Presenter instances.

  @Override public void showAllContacts(List contacts) {
    ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new CardPresenter());
    for (Contact contact : contacts) {
      arrayObjectAdapter.add(contact);
    }
    HeaderItem headerItem = new HeaderItem(getString(R.string.contacts_item_title), "");
    rowsAdapter.add(new ListRow(headerItem, arrayObjectAdapter));
  }

  @Override public void showSearchResultContacts(String query, List contacts) {
    ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new CardPresenter());
    for (Contact contact : contacts) {
      arrayObjectAdapter.add(contact);
    }
    String resultSearchTitle = getString(R.string.search_result, query);
    HeaderItem headerItem = new HeaderItem(resultSearchTitle, "");
    rowsAdapter.add(new ListRow(headerItem, arrayObjectAdapter));
  }

If your user interface has to react to user clicks you’ll have to provide a OnItemViewClickedListener implementation to the DetailsFragment.

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setSearchResultProvider(this);
    rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
    setOnItemViewClickedListener(new OnItemViewClickedListener() {
      @Override public void onItemClicked(Presenter.ViewHolder viewHolder, Object item, RowPresenter.ViewHolder presenterViewHolder, Row row){
        presenter.onContactClicked((Contact) item);
      }
    });
  }

Without Leanback library:

If your user interface has to be built on top of non so common user interface widgets you’ll have to review some important concepts:

  • Focus usage is going to be fundamental, learn how to use nextFocusX attributes in your layout widgets and how to use <requestFocus/> label.

 

    

  


  • State List Animators are going to help you to highlight some of your widgets when a widget is selected.


  
    
      
      
      
      
    
  
...

  • getCurrentFocus()” method will give you the UI widget selected by the user and is going to be really useful if you have to change your UI programmatically. OnFocusChangedListener is also really interesting to provide dynamic UI changes.
  @Override public void hidePreviousPasswordElements() {
    View currentViewWithFocus = getCurrentFocus();
    ViewGroup parent = (ViewGroup) currentViewWithFocus.getParent();
    for (int i = 0; i < parent.getChildCount(); i++) {
      View ib_element_item = parent.getChildAt(i);
      if (currentViewWithFocus.equals(ib_element_item)) {
        ImageButton previousFocusedElement = (ImageButton) parent.getChildAt(i - 1);
        updatePasswordElementWithAsterisk(previousFocusedElement, 0);
      }
    }
  }

    @OnFocusChange(R.id.iv_app_logo) void onFocusChanged(boolean focused) {
    if (!focused) {
      iv_app_logo.setFocusable(false);
    }
  }
  • Displaying recommended content using the Android Notifications API will help you to show important content to your users. It’s as easy as displaying an old Android notification.
    Notification notification = new NotificationCompat.BigPictureStyle(
        new NotificationCompat.Builder(context).setContentTitle(title)
            .setContentText(description)
            .setPriority(priority)
            .setLocalOnly(true)
            .setOngoing(true)
            .setColor(context.getResources().getColor(R.color.primary_color))
            .setCategory(Notification.CATEGORY_RECOMMENDATION)
            .setLargeIcon(image)
            .setSmallIcon(smallIcon)
            .setContentIntent(pendingIntent)
            .setExtras(extras)).build();

    notificationManager.notify(id, notification);

Conclusions:

Create a sample application based on the Tuenti product has been a really interesting challenge, not just from the coding point of view, but an interesting challenge from the UX, UI and Product point of view. User interaction with this new set of devices is really different, and how you have to base the UI state in the application focus is a different way to think in an Android device. The usage of the Activity/Fragment background to provide more context to the user and use scale up/down animations to guide the user navigation through the application was really funny. From the development side, to code for Android TV is really easy if your UI has to use Leanback base components, but if you want to provide a different user experience based on a different user interface you’ll have to do your best and start using state list animators and other new features from API 21. Review also some base components of Android TV Leanback library could be interesting to provide a customized UX but based on the original components. Review RowsFragment and HeadersFragment if needed. Explaining in this blog post how to use the full Leanback library to build applications for Android TV would be impossible, so I recommend you to take a look at the Github repository we have prepared for you and to review the usage of some non familiar elements like ArrayObjectAdapter, HeaderItem objects or other minor details like how to configure the application icon for Android TV.

Developing browser extensions: why and how

Publicado el 03/2/2015 por Joaquín Engelmo, Software Engineer

New year, new theEvnt workshop at Tuenti. It took place in our office last Saturday. This time Otogami’s CTO Rock Star Jerónimo López, a.k.a. @jerolba on Twitter, talked about developing browser extensions.

First, he reviewed browser extensions history and what options we do have when we face this challenge in the existing browsers. After that master class, we got our hands dirty creating a Chrome extension following official documentation and Jero’s tips and tricks. You can check the slides and the code and create your first awesome extension. Finally, Jero showed us Otogami’s extension for Chrome and Firefox, PowerUp, which simplifies the way users find cheaper games in their searches for the latest ones.

As in previous editions, we really appreciate you all attending the 4 hour workshop and your positive energy spending a #SaturdayCoding ;) As for us, we’re already preparing the next workshop related to iOS with our great engineering team. You can check all the updates in the workshops web, and following theEvnt or the Tuenti Engineering accounts on Twitter.

Devops is dead. Long live Devops

Publicado el 23/1/2015 por Óscar San José, Principal SRE Engineer

Once again, Tuenti had the priviledge of hosting a meetup event in its offices! This time the guest star was Antonio Peña who came with a talk for the Madrid Devops group, called "DevOps 101". Antonio is a passionate advocate of the Devops culture, and this talk was intended as an introduction to the field.

He described the theoretical principles and practical advantages of the approach for those not knowing them. He continued stirring the controversy about the identity crisis and the "death of Devops", and gave us his particular point of view about this. He also talked us about some interesting thoughts by the most prominent international figures of the Devops scene, including famous names such as Seth Vargo and Katherine Daniels (you are already late if you are not following them on twitter yet: @SethVargo and @beerops), and how their ideas have not always been accepted easily by the community (including, in the case of Seth, death threats for his disruptive ideals!*).

He finished giving an overview regarding the Devops movement in Spain, and how any one of us developers can be a kickstarter of the devops culture here in our own jobs.

Next there was an interesting Q&A session, were participants were abel to express their doubts and hopes, and where some people, including Tuenti developers such as Pedro Gómez and myself were able to share their own experiences working on a Devops-loving environment.

Finally, the most important part of any meetup event: beers! Where a lot of interesting ideas were shared and discussed around an informal, yet thrilling, atmosphere. As you know, participation is the key, and highly encouraged!

As you already know, we are open to hosting other user groups meetings and talks so, if you’re interested in organising a tech or design-related event, get in touch! ;)

*As Antonio points out, Seth Vargo did not receive life threats for his opinions about devops, but rather fow his work as developer: https://sethvargo.com/leaving-chef/

Pages