Drupal 8: Solution for Mobile Detect Cache Context

Abstract hero image

In theming the Drupal Commerce site Zeederberg Leather, on the product page, the mobile view was significantly different to the desktop view. This was in layout as well as function.

Image
Desktop view of product layout
Desktop layout for procuct
Image
First layout for product on mobile
Layout for product on mobile
Image
Alternate layout for product on mobile
Alternate layout for product on mobile

Mobile Detect to the rescue!

It is possible in Drupal 8 to load a specific Twig template based on a mobile detect process, however I did not want to use such a extensive solution. I searched around and discovered the Mobile Detect module.

This seemed exactly what I needed. Why?

  • For preprocess I could use:
    $md=\Drupal::service('mobile_detect');
    $is_mobile=$md->isMobile();
    $is_tablet = $md->isTablet();
  • The Drupal 8 module extends Twig Extensions module thus in my Twig template I can use:
    {% if is_mobile() %}
    {% if is_tablet() %}
    {% if is_device('iphone') %} 
    {% if is_ios() %}
    {% if is_android_os() %}

All went fine until I tested on Desktop and Mobile. If I went to the page on desktop first, the page on mobile was blank. If I went to the page on mobile first the page on desktop was blank. At the time of writing this, the current Drupal 8 8.x-2.0 version of the Module does not have cache support. There is an issue on it here.

Resolving the issue

tarik.cipix's snippet (#11) helped me greatly and I was able to use Mobile Detect with the following code in my custom module:

  • The Twig template: since I wanted the layout for the product display to be different depending on device, I targeted the commerce-product.html.twig template. I had the following:
    {% if is_mobile() %}
       {# small screen layout layout #}
    {% else %}
       {# large screen layout #}
    {% endif %}
  • Adding Cache Context to the detections: In my custom module I had the following preprocess:
    /**
     * Implements hook_preprocess_HOOK()
     *
     * hook_preprocess_commerce_product()
     * add cache context for the mobile detect module
     * reference: https://www.drupal.org/project/mobile_detect/issues/2627504
     */
    function MyModule_preprocess_commerce_product(array &$variables) {
      // Add cache context so both versions are saved.
      $variables['#cache']['contexts'][] = 'MyModule_context_mobile_detected';
    
      // Add classes if device is mobile or tablet.
      $detect = \Drupal::service('mobile_detect');
      if ($detect->isMobile()) {
        $variables['attributes']['class'][] = 'is--mobile';
      }
      elseif ($detect->isTablet()) {
        $variables['attributes']['class'][] = 'is--tablet';
      }
      else {
        $variables['attributes']['class'][] = 'is--desktop';
      }
    }
  • Define the new Context: I define the new Context in a service in my custom module:
    services:
      cache_context.ze_classy_context_mobile_detected:
        class: Drupal\ze_classy\Cache\Context\MobileDetectCacheContext
        arguments: []
        tags:
          - { name: cache.context }
  • Add the Context: Add the context in my custom module in:
    src/Cache/Context/MobileDetectCacheContext
    The code looks something like:
    ‹?php
    
    namespace Drupal\MyModule\Cache\Context;
    
    use Drupal\Core\Cache\CacheableMetadata;
    use Drupal\Core\Cache\Context\CacheContextInterface;
    
    /**
     * Class MobileDetectCacheContext.
     */
    class MobileDetectCacheContext implements CacheContextInterface {
    
      /**
       * Constructs a new MobileDetectCacheContext object.
       */
      public function __construct() {
    
      }
    
      /**
       * {@inheritdoc}
       */
      public static function getLabel() {
        return t('Mobile Detect');
      }
    
      /**
       * {@inheritdoc}
       */
      public function getContext() {
        $md = \Drupal::service('mobile_detect');
        if ($md->isMobile() || $md->isTablet()) {
          return TRUE;
        }
        return FALSE;
      }
    
      /**
       * {@inheritdoc}
       */
      public function getCacheableMetadata() {
        return new CacheableMetadata();
      }
    
    
    }
    

The modules I have used

Use Composer to require and Drush to enable the following module:

  composer require drupal/mobile_detect
  drush en -y mobile_detect
  • Mobile Detect (mobile_detect)
    This is a lightweight mobile detection based on the Mobile_Detect.php library, which can be obtained from the GitHub repository.

    This module is intended to aid developers utilizing mobile-first and responsive design techniques who also have a need for slight changes for mobile and tablet users. An example would be showing (or hiding) a block or content pane to a particular device.

    This module is not intended (and never will be enhanced) to provide theme switching or redirection; other modules already provide this functionality.

My research

This article was aided by the following excellent contributions:

Comments

Permalink

Does this work for anonymous users? We use varnish implemented this code but anonymous users keep getting mobile content on desktop.

Permalink

I have installed the latest version of this module and still it has issue with the internal page cache module. It does not work with internal page cache module enabled.(i.e for anonymous users).

About the cache contexts:

By design, the "Internal Page Cache" core module assumes that all pages served to anonymous users will be identical, regardless of the implementation of cache contexts.

If you want to use the mobile_detect cache contexts to vary the content served to anonymous users, "Internal Page Cache" must be disabled, and the performance impact that entails incurred.

https://www.drupal.org/project/mobile_detect

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.