Liip Blog https://www.liip.ch/en/blog Kirby Tue, 19 Mar 2019 00:00:00 +0100 Latest articles from the Liip Blog en Easily save your Android ViewModel state https://www.liip.ch/en/blog/easily-save-android-viewmodel-state https://www.liip.ch/en/blog/easily-save-android-viewmodel-state Tue, 19 Mar 2019 00:00:00 +0100 Last week, Google released ViewModel Savedstate 1.0.0. Its documentation states:

ViewModel objects can handle configuration changes so you don't need to worry about state in rotations or other cases. However, if you need to handle system-initiated process death, you may want to use onSaveInstanceState() as backup.

When an Activity is destroyed because of memory pressure, the state of the ViewModel is not saved. When the Activity is recreated, the ViewModel will have lost data that was only stored in memory. This is a problem that we often encounter, so we came up with a custom solution. But now Google released its official library to handle this case and I was happy to play with it.

There is already a nice deep-dive article about it, but I wanted to take a step further. I wanted to make it so easy that you don’t even have to think about saving your state.

Define ViewModel variables like you are used to

With my collection of ViewModel SavedState Helpers, you can define your ViewModel like if it was a local variable.

// Import the library
import ch.liip.viewmodelsavedstatehelpers.*

// Define a ViewModel that takes a SavedStateHandle in argument
class MainViewModel(handle: SavedStateHandle) : ViewModel() {
    // Simple string that is saved in the SavedState
    var manualText by handle.delegate<String?>()

    // MutableLiveData that is saved in the SavedState
    val liveDataText by handle.livedata<String?>()
}

You can then use the ViewModel like you would do usually. Your data is saved and restored automatically!

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Obtain the ViewModel with SavedStateVMFactory
    viewModel = ViewModelProviders.of(this, SavedStateVMFactory(this)).get(MainViewModel::class.java)

    // Observe the livedata
    viewModel.liveDataText.observe(this, Observer {
        liveDataText.setText(it)
    })

    // Save the values
    button.setOnClickListener {
        viewModel.liveDataText.value = liveDataText.text.toString()
    }
}

How does it work ?

This is heavily related to our work on SweetPreferences. I basically use Kotlin delegated properties to wrap the calls into a SavedStateHandle, which is the main class offered by the ViewModel Savedstate library.

Take a look at the only file to see the whole code. Then install the library from Github and enjoy your peace of mind when it comes to saving state!

]]>
Drupal Mountain Camp 2019 https://www.liip.ch/en/blog/drupal-mountain-camp-2019 https://www.liip.ch/en/blog/drupal-mountain-camp-2019 Tue, 19 Mar 2019 00:00:00 +0100 I was invited to join a panel discussion on open source as well as giving a talk about diversity&inclusion at Drupal Mountain Camp which I gladly accepted. I brought the entire family along to enjoy the mountain scene together, however this meant that my time at the conference was somewhat limited but I did enjoy lots of interesting discussions on the "hallway track" on Saturday afternoon.

The open source panel turned out to be a very interesting discussion. We covered topics around how we personally deal with juggeling open source with work and private life. The good news is that at least among the panelists a fair amount of open source involvement is done as part of paid work hours. However it is still tricky to make customer realize the long term benefits of getting code for their project released as open source by getting code reviewed and new features and updates via the community. As as a result companies still often offer discounts or other incentives to get permissions to release code as open source. At Liip we have a general provision in our contracts that permits us to release non customer specific code as open source but often we end up doing the work of open sourcing outside of the project budget.

In order to sustain open source we also talked about how to get new people into open source specifically by getting more people to do open source at work. I noted that given the lack of diversity in open source, which is actually worse than in the proprietary world, the main thing to do is to adress the issues on that front (more on that in my talk about diversity&inclusion)!

We also discussed out to especially get the public sector to use open source more. We noted the increasing hurdles like ISO certifications but also the lack of lobbying to compete with commercial sales pitches. At the same time its clear that for citizens there is a big advantage to using open source: no long terms lock-in to pay high license fees, possibilities for inter-cantonal collaboration, more control over the data and business logic to enforce Swiss privacy laws and more money staying in Switzerland rather than getting scooped up by foreign multi nationals. We need more open source advocactes to help bridge this knowledge cap in the public sector!

In my keynote talk Diversity & Inclusion: Why and How? I had to opportunity to talk to a packed room albeit with a slightly tired audience that already had multiple days of talks under their belt. Still I was happy to see so many people choose not to head home early to avoid getting stuck in traffic on the way home. I started the talk with running the video of the racist soap dispenser. The key point I wanted to make is that while the engineers that created this soap dispenser are probaly not racist, the machine they built is. So when people get pointed out that something they did offended someone, do not get defensive as we well make mistakes, simply "listen, learn, move on". I also gave a very concrete examples from the Symfony community where we also screwed up. I then covered several key terms like "diversity", "inclusion", "social priviledge", "equity" and "cognitive biases".

Cognitive biases deserve particilar attention. They are essentially mental shortcuts that keeps us on a safe path rather than exploring the benefits of new ideas, which are key for innovation. Additionally cognitive biases push us to rationalize favoring less diversity. In contast being exposed to more diversity helps us challenge those mental shortcuts and thereby help giving innovative thinking more opportunity. In this spirit diversity isn't about comfort, indeed being faced with different ideas can be quite uncomforable. I wrapped up my talk with lots of practical examples of how we can work towards diversity and inclusion by doing small changes in our behavior. I challenged the audience to try and pick at least one or two items from the list to implement right away. The video recording will soon be published in the mean time have a look at the slides.

I would like to thank the Drupal Mountain Camp for organizing this event in such a scenic location and giving space for these important topics.

]]>
Training apprentices https://www.liip.ch/en/blog/training-apprentices https://www.liip.ch/en/blog/training-apprentices Mon, 18 Mar 2019 00:00:00 +0100 The subject was highly technical and there was a crowd of people from all around the Romandie to listen. Let’s be honest, when he started talking, I was hella proud of him and the successful developer he has become. Was that thanks to me, his apprenticeship coach? Of course not! The only person he needs to thank to is himself and his discipline in learning and progressing every day. He earned his success alone and I congratulate him for that!

What is the point of teaching apprentices?

In my opinion, development is closer to a form of art than to technical work. And I don't think art can’t be taught; it has to be found inside oneself and develops with time. The technical part of programming is by far the least important thing I am teaching my current apprentices, as this becomes almost natural after the first months. Martin Fowler once said «Any fool can write code that a computer can understand. Good programmers write code that humans can understand.». This couldn’t be more appropriate when learning how to code. In the beginning their realizations are complex blobs of terrible code 🤭.Review after review, the code becomes cleaner, simpler and readable. Eventually the solutions apprentices find tend to be unique and reflecting their own personality. Even though you didn’t even think of doing it their way, you have to admit it’s a pretty cool way to do it...
The trickiest thing to teach are

  • the understanding of why we do things
  • how to make complex things simple
  • how to find astute solutions with elegance
  • how to write code that is maintainable in the long term

Those aren't fixed goals to achieve, nor is it something that can be read in books: it's a work philosophy. I found that the best way to achieve this is to work in real world situations, and let them craft code that is used and appreciated. Working with a purpose matters more than everything else. Fortunately by helping us develop our internal tools, they can do that with less pressure and less fixed deadlines compared to client projects. In these conditions magic can and will happen!

The value of coaching

In many companies, apprentices are considered to be cheap employees only. Trying to catch bits of wisdom from the all-knowing “seniors” who don’t have time for them is not helpful either. Often they are going to work because they have to. Without a learning environment, they are left to their own and miss critical parts of the knowledge needed in the future. I despise this approach and am saddened by the associated waste of talent.

We integrate the apprentices into meaningful tasks right from the start and give them time to find their way around. Simply put, we consider an apprentice as a colleague who needs more time to get things done and try their own way. Sure, it requires patience, and sometimes the ability to explain the same thing 5 times in 5 different ways, but nothing is for free. When you have successfully explained what a javascript closure is to a beginner, you have reached your daily(or weekly) target!
Eventually, it’s them who will teach you technical things, and it happens faster than you’d like to admit. If you still think that teaching is a waste of your time, think about the fact that it might be the best and only way to ensure that the future developers applying to be your colleagues have been taught in a sensible way. It can be a challenging, sometimes frustrating, tedious job. But the feeling you get when they succeed is fantastic!

]]>
Wie ich meine Lehrzeit bei Liip erlebe https://www.liip.ch/en/blog/wie-ich-meine-lehrzeit-bei-liip-erlebe https://www.liip.ch/en/blog/wie-ich-meine-lehrzeit-bei-liip-erlebe Thu, 07 Mar 2019 00:00:00 +0100 Sophie: Hast du bewusst die Firma Liip ausgesucht?

Sonja: Liip war das erste Unternehmen, welches mir zugesagt hat nach einer Vielzahl von Absagen. Somit war es eher Zufall. Mir war zu diesem Zeitpunkt noch gar nicht bewusst, dass Liip Holacracy als Organisationsstruktur hat. Ich fand die Arbeitsbedingungen sehr fair und ansprechend. :-)

Was macht Liip?

Liip ist eine Softwareentwicklungsfirma, welche an 5 Standorten in der Schweiz vertreten ist. Das Unternehmen stellt Webseiten und Mobile Applikationen für diverse Kunden, wie die Migros oder auch Banken her.

Was unterscheidet Liip von anderen Firmen deiner Meinung nach?

Ich finde Liip arbeitet sehr viel aus Sicht der Kunden, wir holen uns regelmässig Feedback. Diese Denkweise verdanken wir unserer Unternehmensstruktur. Was für mich Liip im Vergleich zu anderen Firmen ausmacht, ist das familiäre Arbeitsklima.

Wie würdest du jemandem Holacracy erklären, der noch nie davon gehört hat?

Holacracy erinnert mich immer etwas an die Schweiz mit ihren Kantonen und der Gemeinden. So haben wir ein Unternehmen (die Schweiz), mit verschiedenen Circles (wie Kantone) und darin Rollen (wie Gemeinden). Die Rollen erledigen Aufgaben für die Circles. Anders als die Schweiz ist Holacracy nicht demokratisch, aber jeder darf seine Meinung äussern, das ist sogar erwünscht und wir erarbeiten gemeinsam ein Ziel.

Wie ist das Arbeitsklima bei Liip?

Wir duzen uns alle und der Umgang ist sehr familiär. Das wohl unserer Mitarbeitenden ist allen sehr wichtig und es gibt Möglichkeiten um sich vom stressigen Arbeitsalltag zu erholen wie zum Beispiel Massagen und Yoga. Ich finde es super, dass wir in Teams arbeiten.

Wie sieht ein typischer Tag von dir aus?

Morgens checke ich als erstes meine Mails und Slacks (firmeninterner Chat). Danach schaue ich im Buchhaltungsprogramm nach, ob wir Rechnungen zu Verbuchen haben. Im ersten und zweiten Lehrjahr hat man bei Liip einen Einblick in die Administration, und im dritten hat man ein noch Einblicke in die Finanzen und das Personalmanagement.

Was gefällt dir am Besten bei diesem Unternehmen?

Mir gefällt sehr viel, ich hätte mir kaum ein besseres Unternehmen vorstellen können für meine Persönlichkeitsentwicklung. Es macht sehr viel Spass bei Liip zu arbeiten, die Aufgaben welche ich zu erledigen habe, sind abwechslungsreich.

Welche Fähigkeiten benötigt man bei Liip?

Ich finde man braucht Flexibilität, da es vorkommen kann, dass man an verschiedenen Standorte reist. Ausserdem sind Sprachkenntnisse von Vorteil, aber kein Muss, da man die Sprachen bei Liip sehr viel benötigt. Die Unternehmenssprache ist Englisch, da wir an den Standorten in der Westschweiz Französisch sprechen und an den Standorten in der Deutschschweiz Deutsch sprechen. Ich finde Teamfähigkeit und Kommunikationsfähigkeit ebenfalls sehr wichtig, wobei ich mir das erst im Verlauf meiner Lehrzeit angeeignet habe.

Was hast du bei Liip gelernt?

Ich habe eine Menge gelernt bei Liip, meine Kommunikationsfähigkeiten habe ich beispielsweise verbessert, da ich in meinen Schwächen unterstützt und meine Stärken gefördert wurden.

Entdecke Sophie's Antworten auf Französisch zu meinen Fragen hier.

]]>
Easy Picture Sharing On Android https://www.liip.ch/en/blog/easy-picture-sharing-on-android https://www.liip.ch/en/blog/easy-picture-sharing-on-android Thu, 07 Mar 2019 00:00:00 +0100 PicShare

When letting your user share a picture, you might want to add a watermark or reduce the size of the image. At Liip, the case that we encountered was that we wanted to share an image and some text with it. Turned out many applications won't support getting text and image at the same time. We chose to add the text inside our image directly, and no solution existed that worked out of the box.

We ended up making a library with more options for image sharing, get it on github or read a bit more about it here

How basic sharing works

When you want to share some data, you simply start an activity with the built-in intent type ACTION_SEND:

val sendIntent: Intent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, "This is my text to send.")
    type = "text/plain"
}
startActivity(sendIntent)

Alternatively, for an image, you would use:

    putExtra(Intent.EXTRA_STREAM, uriToImage)
    type = "image/*"

Tip: The support library now includes a helper for sharing (see ShareCompat)

The common problem with these solutions is that you have to define the type of data you send (using text/plain or image/* for example). This type will be used by Android to define which app can be proposed to the user when sharing your data. Or course, you can set the type to */* but this leaves the responsibility to the called app to identify the type of data sent. Few apps support it and you have no control over how they will interpret it.

What does the PicShare lib do?

PicShare ultimately only allows you to share images. But it will let you crop it, inlay as many text and visuals onto it and then present a preview to your user. All the flow between these different screens is handled by the lib so you have nothing else to do.
Simply call the startSharing function with the desired options:

val cropOptions = CropOptions()
                    .setFixedSize(1024, 1024)

val inlayOptions = InlayOptions(InlayViewProvider(R.layout.inlay))

val previewOptions = PreviewOptions()
                    .setTitle(resources.getString(R.string.picshare_app_title))

startSharing(this, 1024, 1024, "my share panel", "my shared image", cropOptions, inlayOptions, previewOptions)

Under the hood 1: Workflow

When called, Picshare will start an activity with no layout, for the sole purpose of handling the workflow. This activity will call an image selection app, or any other view necessary for sharing your image, and listen for the results in onActivityResult. It will then choose what is the next view to present, and so on until the sharing is complete or canceled.

Under the hood 2: Content Provider

PicShare doesn't require any permission. The image selected by the user for sharing is copied in a folder accessible by a content provider. PicShare reads the selected image from the MediaStore to get the bitmap but doesn't save it back there for sharing. This means that it is not written on public storage and won't be accessible by other apps (like the gallery) while it is stored there before sharing. Using a content provider is a good practice on android and is a nice way to let other apps access some of your media without writing it on public storage. Here is how you can store files and share them using a file provider:

You can get a Uri by doing the following. This uri is readable and writable by your app only. If you print it you will see that it starts with file://.

var file = File("${context.cacheDir}/myFolder", "myFileName")
var localUri = Uri.fromFile(file)

Tip: Files in context.cacheDir are accessible by your app only.

To get a uri that other apps can read if you share it, you need to use a content provider in the following way. This uri can be shared. If you print it you will see that is starts with content://. But in order for this to work, "${context.packageName}.provider" must be an existing file provider referenced in your manifest:

var file = File("${context.cacheDir}/myFolder", "myFileName")
val shareUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)

The file provider itself is defined in its own xml file. The manifest will list all your file providers.

fileprovider.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <cache-path name="myfileprovider" path="myfileprovider"/>
</paths>

manifest.xml

<provider
    android:name="com.company.appname.sharing.SharingFileProvider"
    android:authorities="com.company.appname.provider"
    android:exported="false"
    android:grantUriPermissions="true"
    android:writePermission="false">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/fileprovider" />
</provider>

Under the hood 3: Pass a custom view to an activity

In PicShare, you can create an xml layout to inlay the user image with. This seems simple but there is a trick. We cannot pass an already inflated view to an activity. And if we just pass the layout resource id, PicShare won't know how to dynamically customize it once inflated. The trick here is to use a Serializable class. Instances of Serializable classes can be passed in bundles to activities. In PicShare, we prepared an open class that is serializable and contains everything we need to inflate it later.

open class InlayViewProvider(val viewResourceId: Int) : Serializable {
    open fun populate(view: View, context: Context) {
    }
}

The user of the lib simply adds their own custom subclass instance to the inlay parameters:

var inlayOptions = InlayOptions(InlayCustomProvider(R.layout.inlay))

Behind the scene, PicShare will bundle it and retrieve the view from the launched activity. Once inflated, it can populate it with the populate function defined by the custom implementation:

var inlayViewProvider: InlayViewProvider
        get() = optionBundle.getSerializable(inlayViewProviderKey) as InlayViewProvider
        private set(provider) = optionBundle.putSerializable(inlayViewProviderKey, provider)
val view = layoutInflater.inflate(inlayViewProvider.viewResourceId, null)
inlayViewProvider.populate(view, this)

Conclusion

PicShare will help you share custom images from you app with just a few options. It is available on github and can easily be added in your app. It is distributed under the MIT licence.

]]>
Comment est-ce que je vis mon apprentissage chez Liip ? https://www.liip.ch/en/blog/comment-je-vis-mon-apprentissage-chez-liip https://www.liip.ch/en/blog/comment-je-vis-mon-apprentissage-chez-liip Thu, 07 Mar 2019 00:00:00 +0100 Sonja: comment décrirais-tu ce que fait Liip ?

Sophie: Liip est une entreprise qui fait des sites Web et des applications Mobile pour différents clients, comme des grandes entreprises ou des plus petites organisations.

Lorsque tu es arrivée chez Liip, quelles ont été tes premières impressions ?

Dès que je suis arrivée pour réaliser mon stage, tout le monde m’a très bien accueillie et est venu se présenter. J’ai vu dès la première fois que j’y ai posé un pied, que Liip n’est pas comme les autres entreprises et que tout est beaucoup plus familial et plus ouvert. J’ai eu l’impression que tout le monde se sent comme chez soi et se respecte. De plus, Liip est très moderne et je pense que, pour des jeunes femmes, cela est très bien adapté.

A ton avis, qu’est-ce qui différencie Liip des autres entreprises formatrices ?

Liip n’est pas comme les autres entreprises, parce que nous travaillons avec une autre structure; Holacracy. C’est-à-dire que nous (les Liipers) travaillons de manière autonome et à notre propre rythme. Cela demande aussi d’être organisée et d’avoir une discipline personnelle. Ça me plaît beaucoup de pouvoir réaliser mon apprentissage dans une entreprise comme celle-ci.

Qu’est-ce qu’est Holacracy et comment me l’expliquerais-tu si je ne m’y connaissais pas ?

Holacracy n’est pas une structure comme les autres. C’est une structure auto-organisée, ce qui veut dire que nous n’avons pas de supérieur hiérarchique, donc pas de “chef”. Chaque Liiper rempli un ou plusieurs rôles concernant l’organisation de Liip. C’est une structure horizontale, constituée de cercles. Par exemple, il y a un cercle par bureau (Berne, Fribourg, Lausanne, Zürich et St-Gall) regroupant les Liipers travaillant dans ce bureau. Les Liipers sont aussi regroupés dans des cercles d’expertises (informaticien-ne-s, finances, administration, etc.) et également des cercles de projets (en fonction des projets clients).

Et, comment est-ce que tu te sens chez Liip ?

Je me suis sentie bienvenue et très à l’aise dès mon arrivée en août 2018. Notre culture très familiale et notre atmosphère très agréable font de nous une entreprise pleine de confiance. J’ai le sentiment que Liip essaie de satisfaire chacun de ses collaborateurs-trices, pour les garder le plus longtemps possible.

A quoi ressemble une de tes journées et quelles sont tes activités préférées ?

Mes journées commencent à 8:00. En premier, je me dirige à la poste afin de chercher nos lettres reçues dans notre case postale. Arrivée au bureau, je commence à distribuer les différentes lettres aux gens responsables. Plus tard, je regarde ma boîte mail et mes messages reçus. D’après le jour, je dois accomplir plusieurs tâches comme: acheter des fruits et du thé, organiser des cadeaux, commander du matériel de bureau, etc. Je dois également répondre au téléphone et accueillir nos clients. Je n’ai pas vraiment “d’activités préférées” mais ce qui me plaît beaucoup, c’est l'organisation et l’achat des cadeaux pour les Liipers ! :-)

Qu’est-ce qui te plait le plus chez Liip ?

Comme déjà mentionné plusieurs fois, je trouve que cette entreprise est incroyablement bien organisée et structurée. Chaque Liiper sait de quoi il/elle est responsable et de quoi il/elle doit se charger. Nous avons tous besoin d’une grande volonté. Et l’envie de contribuer au développement de Liip doit tous nous animer.

De quelles compétences as-tu besoin pour travailler dans cette entreprise ?

Je ne pense pas que l’on ait besoin de « compétences très spécifiques » pour être apprenti-e chez Liip. Je pense par contre qu’il est nécessaire d’être intéressé-e et de savoir parler l’allemand et le français. L’anglais doit être compris aussi parce que c’est l’une des langues avec lesquelles nous communiquons aussi régulièrement. Je pense qu'il faut être une personne sympathique et ouverte à rencontrer de nouvelles personnes, à faire de nouvelles expériences et à d’élargir ses connaissances en se formant.

Qu’est-ce que tu as appris depuis que tu as commencé ton apprentissage chez Liip ?

J’ai beaucoup appris. J’ai appris à être efficace et plus précise. Liip m’a permis d’augmenter ma vitesse de travail. J’ai aussi appris à travailler de manière autonome ainsi qu’en groupe. Liip m’a permis de m’ouvrir vers d’autres gens et de m’exprimer plus facilement et sans hésitation.

Découvre aussi l'interview en allemand de Sonja ici.

]]>
#teamhuman an der Shift 2019 https://www.liip.ch/en/blog/teamhuman-shift2019 https://www.liip.ch/en/blog/teamhuman-shift2019 Wed, 06 Mar 2019 00:00:00 +0100 Carte Blanche Shift 2019, 10 Minuten zum Abschluss, von Hannes Gassert:

Kürzlich habe ich einen Kaffee getrunken mit einem führenden Digital-Unternehmer und Risikokapitalgeber, ein Big Shot der digitalen Szene hier bei uns in Zürich. Wie viele der Player drüben im Valley beschäftigt er sich stark mit dem Moment, in dem uns all das hier um die Ohren fliegt. Er erzählte mir davon, dass wegen der Politik, dem Klimawandel und der Migration womöglich bald alles zusammenbreche. Die Gesellschaft. Die Kultur. Der Staat. Und damit auch seine Firmen, wohl auch sein Haus mit Blick über den See. Der Digitale Big Shot erzählte mir dann — von Neuseeland.

Neuseeland sei super. Weit weg von allem, vom Klimawandel wenig betroffen, mit nur wenig Geld erhalte man die Staatsbürgerschaft. Und Flüchtlinge seien auch keine Thema. Viel zu weit weg für Schlauchboote.

Meine Bitte an ihn: bitte bleib hier. Nutz deine Macht und dein Kapital und deine Technologie, nicht um möglichst die Verbindungen zu kappen, weg zu kommen von all den Problemen.

Mein Bitte an Sie: Bleiben Sie hier. Nutzen Sie Ihre Macht und Ihr Kapital und Ihre Technologie. Und ich meine das ganz sicher nicht nur örtlich. Bitte bleiben Sie unter uns Menschen, seien Sie im Team Mensch! Sicher Ich weiss, das ist schwierigen mit den Menschen. Ohne wär's viel einfacher :) Wenn Sie einen gescheiten Business Plan haben, machen Sie's ohne. Braucht Geld, braucht Zeit, und dann stellen die noch Sinnfragen. Schlechtes Geschäftsmodell. Skaliert nicht! Oder wenn's denn sein muss: möglichst in ein Land damit, wo die wenigstens nicht so viel kosten.

Bleiben Sie hier. Gerade wenn es um die Entwicklung strategischer IT geht.

Wenn Sie Virtual Reality oder Sachen machen wie Markus von Disney am Morgen: bleiben Sie unter uns Menschen, wie immer das hinzukriegen ist. Denn "digital responsibility" darf kein digitales Neuseeland schaffen, schön helfen die Augen zu verschliessen und die Geflüchteten auszublenden, den Klimawandel, den Zusammenbruch. Bleiben Sie hier.

Ich bin auch sicher, es hat hier unter uns Transhumanisten, die sich auf die Singularität vorbereiten, den Tag wenn die künstliche Intelligenz übernimmt, exponentiell intelligenter wird und uns nur eines bleibt: in der Stafette der Evolution den Stab weitergeben, den Menschen elegant in den Hintergrund gleiten zu lassen und das letzte Schiff zu nehmen ins ultimative Neuseeland: den Upload. Hoffen Sie, dass Sie dann zu denen gehören, die sich diese Digitalisierung, diesen finalen Upload leisten können. Und hoffen Sie, dass der Upload nicht bei 99% abbricht!

Oder aber eben: bleiben Sie hier.

Bleiben Sie hier, seien Sie Teil vom "Team Mensch", und lassen Sie uns etwas sinnvolles machen.

  • Kann heissen: Bleiben Sie hier und helfen Sie, eine Digitalisierung bauen, die echten, menschlichen Wert schafft. Bei unserer Softwareschmiede Liip sagen wir dem: Digital Progress, mit einer grossen Aspiration, zu was Fortschritt eigentlich sein muss, ökologisch und sozial. Denn Fortschritt ist mehr als "Innovation". Fortschritt ist mehr als Effizienzgewinn.

  • Kann heissen: bauen Sie jetzt Technologie, die nicht nur nicht schadet, sondern tatsächlich was nützt. C02 spart zum Beispiel. Online echten, menschlichen Meinungsaustausch möglich macht. Die Zeit gibt statt Zeit stiehlt. Die Hatespeech eingrenzt statt belohnt. Die nicht "ethisch" und "verantwortungsvoll" ist, weil Sie einfach das ganz übelste Zeug nicht macht.

  • Kann heissen: bleiben Sie hier, seien Sie im Team Mensch, in dem Sie unsere digitale Branche besser, menschlicher machen. Machen sie zum Beispiel mit bei Powercoders, unserer Programmierschule für Geflüchtete. Die nutzt aus, dass man in unserer Branche nicht super Deutsch sprechen muss, wenn man super Javascript spricht. Wir suchen aktuell ein neues Schulhaus hier in Zürich, und wir suchen immer Firmen, die einen Praktikumsplatz anbieten kann für eine junge Frau aus dem Irak, einen jungen Mann aus Tibet, eine Syrierin oder einen Syrer.

  • Kann heissen: nutzen Sie die riesige Power in unserer Branche um zu zeigen, was gute Arbeit heute heissen kann. Geben Sie den Leuten auch ihre Elternzeit, finden Sie andere Möglichkeiten als frustrierende, unmenschliche Hierarchien, um die Komplexität in ihrem Unternehmen zu organisieren. Wir arbeiten noch viel zu oft auf eine Art und Weise, wie wir's schon lange nicht mehr müssten.
    Sagen Sie auch zu einem Projekt mal nein, auch wenn's doch super spannend wär und sich schon noch lohnen würde. Machen wir bei Liip viel. Muss sein.

  • Kann auch heissen: akzeptieren Sie nicht, was Judith heute morgen gesagt hat: der Staat habe keine Ahnung und die Politik keinen Plan. Nicht, dass Sie jetzt alle in die Politik müssten, aber spielen Sie mit, seien Sie Teil der Debatte, wie wir eine Wissensgesellschaft hinkriegen, die im menschlich ist und gut.

  • Und bald kann das auch heissen: werden Sie für ein Jahr beim Staat "Innovation Fellow". Das ist eine Idee, die Obama ganz zu Beginn seiner Amtszeit umgesetzt hat, auf die Erkenntnis bauen, dass der digitale Graben zwischen Silicon Valley und Washington D.C. verheerend tief geworden war, dass die öffentliche Hand abrutschte und den Anschluss verlor. Die Lösung: nicht ein neues Gesetz, sondern Beschleunigung mit den richtigen Leuten zur richtigen Zeit am richtigen Ort: Obama brachte Top-Techies für jeweils ein Jahr direkt zu sich ins Weisse Haus, um in dieser Zeit ein Thema konkret vorwärts zu pushen, neue digitale Skills, Methoden und Werkzeuge in seine Administration zu tragen. Und dann wieder gehen zu können, mit neuem Verständnis auf beiden Seiten und, in vielen Fällen, einer GovTech oder Civic Tech Geschäftsidee.
    Hier bei uns Zürich wird das kommen, werden Sie "Zurich Innovation Fellow" werden können und ein Jahr etwas zurück geben können, ohne für immer Staatsangestellte werden zu müssen. Und gerade heute hat in Bern der Bundesrat das entsprechende Postulat angenommen hat. Da ich sehr froh bin, dass ich diese Idee auf die politische Agenda habe bringen können, musste ich das vorhin sofort tweeten. Falls Sie da vielleicht mal mitmachen wollen: liken Sie den doch!

In jedem Fall: bleiben wir hier, setzen wir uns ein! Lassen Sie uns zusammen im Team Mensch sein und machen wir diese Branche zu einer wirklich menschlichen. Ethischen. Verantwortlichen.

Zurück zu unserem digitalen Top Shot, der sich Neuseeland so genau anschaut. Er wird sich bald die Frage stellen, wie sein Staff, die Putzfrau, die Nanny, die Security Leute denn so drauf sind, wenn die Apokalypse dann da ist. Einfach anständig bezahlt zu sein wird nicht viel helfen, wenn alles brennt, auch nicht in Neuseeland. Vielleicht sollte man mit denen heute schon nett sein.

Seien Sie im Team Mensch. Und kommen sie gut nachhause — aber bleiben Sie hier.

Danke!

Diese Rede ist ein "Remix" von Inhalten von Douglas Rushkoff, seinen Reden und insbesondere seinem Podcast zum Buch Team Human, gut gemischt mit den an diesem Tag für mich wichtigen Punkten. Was diese Fragen für mich und für Liip bedeuten und wie sich daraus eine gute Zusammenarbeit ergeben könnte, das diskutiere ich mit Ihnen sehr gerne.

Bild: Louis Rafael Rosenthal / digitalresponsibility.ch

]]>
Give context to texts by highlighting and explaining important terms https://www.liip.ch/en/blog/give-context-to-texts-by-highlighting-and-explaining-important-terms https://www.liip.ch/en/blog/give-context-to-texts-by-highlighting-and-explaining-important-terms Thu, 28 Feb 2019 00:00:00 +0100 In a recent innovation project, we had the challange to help users better understand news articles. I want to share our approach and findings.

Idea

During a brainstorming session, the idea came up to augment text to give users explainations for terms and abbreviations used in news articles. The inspiration for this was Genius (formerly Rap Genius). Unlike Genius we wanted the terms and explanations to be automatically generated instead of needing users to provide them.

This is a mockup of the proposed feature on a mobile device:

The workflow of a user is as follows:

  • A user is referred to a news article on their social media platform
  • On the news article the terms, abbreviations, persons etc. are highlighted in yellow to indicate "extra content"
  • If a user hovers (on a desktop computer) or clicks (on mobile) on a term, the extra information is displayed

This "extra content" helps the user to navigate the article by showing the important parts and provides help where it could be useful.
If the user is known (e.g. because they are logged it), it would even be possible to personalize the content and terms. So that a topic expert gets more in-depth information than the casual reader.

Architecture

The prototype was setup deliberately as a standalone application with no direct integration of the news site. This was due to the project setup as there were no technical resources available at the client side for this project. Therefore we decided to setup a reverse proxy to get the original news website and inject a bit of JavaScript to augment the page. This approach has been proven to be very good, as the client can see the changes directly on "their" page, without having to deal with their internal IT and possible restrictions. For our innovation project this was exactly right, as it allowed us to move quickly and deliver meaningful results.

We quickly realized that simply invoking some JavaScript was not enough to get the task done, as there are many complex steps until a result is ready. For the prototype we focused on English text only.

These are the components we identified:

  • Reverse proxy - to get the original content
  • Extract text - get the article text from the HTML of the website
  • Named-Entity-Recognition (NER) - extract the important terms/entities from the extracted text
  • Search - provide an explaination of the terms/entities from the NER
  • Highlight - highlight the terms and display the extra content provided by the search

To have a maximum of flexibility, we decided to use a Message queue. All of these steps need an input, can then work independently and produce an output. So using a message queue was a natural choice. The steps can run asychronously and can even be written in completely different languages.

For the sake of this prototype we created multiple implementation for some compontents:

Here is a sequence diagram to explain in more detail how a user gets a result in the prototype:

Note that for the communication between the client (browser) and the server we chose socket.io. It establishes a long-standing connection to send and receive events, if possible it makes use of web sockets.

Findings

Extract text

This is a step that would not be necessary in a production environment, as we would have first-class access to the text.

Named-Entity-Recognition

Depending on the topic of a news article the results varyied a lot. OpenCalais could shine on all economic topics, but lacked knowledge of Swiss politicians or local events. NLTK gets the job nicely done, even though a lot of important terms are still missing. To make sure certain things are always covered (like commonly used abbreviations), we would propose to create a controlled vocabulary and always check for these terms as an additional step.

Search

The search on Wikidata needs more context of the text than we currently provide. The content is high quality, but with our simplistic approach the results are often either too general ("Switzerland is a country in Western Europe") or wrong (e.g. information about a famous person sharing the same lastname as someone in the article).
So the search definitely needs more information to return better results.

Nonetheless this prototype showed a great potential for the general idea, it helps us to develop an architecture to quickly combine components written in different languages and last but not least a clever approach to integrate a prototype in an existing website without interference as you can see on this screenshot:

For details, check the code of the protoype.

]]>
Doctrine and Generated Columns https://www.liip.ch/en/blog/doctrine-and-generated-columns https://www.liip.ch/en/blog/doctrine-and-generated-columns Tue, 26 Feb 2019 00:00:00 +0100 To make this possible, we had to use a generated column to be able to create an index on a field inside a JSON column. Doctrine does not know Generated Columns, so we had to hide the column and index from the Doctrine Schema Generator.

Our use case is that we have a table with orders. We have to build a report that shows sums of the orders by regions (zip codes in our case). The address on the order is not allowed to change, the goal is to record to what address an order has actually been shipped. Rather than linking to a different table with foreign key, we decided to denormalize the address on the order as a JSON MySQL field.

The first approach queried the zip codes table and then looped over the zip codes to query the order database for each of the 3 different sums the report contains. This of course leads to 3*n queries. Add to this that each query is highly inefficient because it needs to do a full table scan because one criteria involves accessing the zip code in the JSON field with MySQL JSON functions. At some point we started hitting timeout limits for the web request to download the export...

Using Subqueries

This is one place where using the ORM for reading is a trap. Writing direct SQL is a lot easier. (You can achieve the same with DQL or the Doctrine Query Builder and hydrating to an array.)

We converted the query into one single query with subqueries for the fields. Instead of looping over the result of one query and having a query for each row in that result, we unified those into one query:

SELECT 
    a.zip,
    (
        SELECT COUNT(o.id) 
        FROM orders AS o
        WHERE o.state = ‘confirmed’ 
          AND JSON_CONTAINS(a.zip, JSON_UNQUOTE(JSON_EXTRACT(o.delivery_address, '$.zip'))
          ) = 1
    ) AS confirmed,
    (
        SELECT COUNT(o.id) 
        FROM orders AS o
        WHERE o.state = ‘delivered’ 
          AND JSON_CONTAINS(a.zip, JSON_UNQUOTE(JSON_EXTRACT(o.delivery_address, '$.zip'))
          ) = 1
    ) AS delivered,
    ...
FROM areas AS a
ORDER BY a.zip ASC

Each subquery still needs to do a table scan for each row to determine which orders belong to which region. We found no fundamentally easier way to avoid having to select over all orders for each row in the areas table. If you have any inputs, please use the comments at the bottom of this page. What we did improve was having an index for those subqueries.

MySQL Generated Columns

Since version 5.7, MySQL supports “Generated Columns”: A column that represents the result of an operation on the current row. Among other things, generated columns are a neat workaround for creating an index on a value stored inside a JSON data field. The MySQL configuration is nicely explained in this article. For our use case, we have something along the following lines:

ALTER TABLE orders 
     ADD COLUMN generated_zip CHAR(4) GENERATED ALWAYS AS
        (JSON_UNQUOTE(JSON_EXTRACT(delivery_address, '$.zip'))
CREATE INDEX index_zip ON orders (generated_zip)

With that, our query can be simplified to be both more readable and use a field where we can use an index:

SELECT 
    a.zip,
    (
        SELECT COUNT(o.id) 
        FROM orders AS o
        WHERE o.state = ‘confirmed’ 
          AND o.generated_zip = a.zip
    ) AS confirmed,
    (
        SELECT COUNT(o.id) 
        FROM orders AS o
        WHERE o.state = ‘delivered’ 
          AND o.generated_zip = a.zip
    ) AS delivered,
    ...
FROM areas AS a
ORDER BY a.zip ASC

So far so good, this makes the query so much more efficient. The rest of this blogpost is not adding further improvements, but explains how to make this solution work when using the Doctrine Schema tool / Doctrine Migrations.

Working around Doctrine

While Doctrine is an awesome tool that helps us a lot in this application, it does not want to support generated columns by design. This is a fair decision and is no impediment for us using them for such queries as the one above.

However, we use Doctrine Migrations to manage our database changes. The migrations do a diff between the current database and the models, and produce the code to delete columns and indices that do not exist on the models.

It would help us if this issue got implemented. Meanwhile, we got inspired by stackoverflow to use a Doctrine schema listener to hide the column and index from Doctrine.

Our listener looks as follows:

<?php

namespace App\EventListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
use Doctrine\DBAL\Events;

/**
 * The orders.generated_zip column and orders.index_zip index have been created
 * with a manually crafted migration as Doctrine does not support generated
 * columns. This listener prevents migrations from wanting to remove the field
 * and index.
 */
class DoctrineSchemaListener implements EventSubscriber
{
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $eventArgs)
    {
        if ('orders' === $eventArgs->getTable()) {
            if ('generated_zip' === $eventArgs->getTableColumn()['Field']) {
                $eventArgs->preventDefault();
            }
        }
    }

    public function onSchemaIndexDefinition(SchemaIndexDefinitionEventArgs $eventArgs)
    {
        if ('orders' === $eventArgs->getTable() 
            && 'index_zip' === $eventArgs->getTableIndex()['name']
        ) {
            $eventArgs->preventDefault();
        }
    }

    /**
     * Returns an array of events this subscriber wants to listen to.
     *
     * @return string[]
     */
    public function getSubscribedEvents()
    {
        return [
            Events::onSchemaColumnDefinition,
            Events::onSchemaIndexDefinition,
        ];
    }
}
]]>
Liip salary system supports self-organization and salary equality https://www.liip.ch/en/blog/liip-salary-system-supports-self-organization-and-salary-equality https://www.liip.ch/en/blog/liip-salary-system-supports-self-organization-and-salary-equality Fri, 22 Feb 2019 00:00:00 +0100 What kind of system do we use?

By the time we founded Liip (2007), we were looking for an easy applicable and adaptable compensation system where social competencies, entrepreneurship and practical experience could be weighted more than diplomas. The salary systems available on the market were not made for a company with our values. So we developed our own system.

We always aimed for transparency at Liip. With the implementation of Holacracy (self-organization) in 2016 we focussed even more on transparency and added the peer evaluation concept to the system. Since about a year, the system is fully transparent: everyone within the company knows who earns which amount and how it is calculated.

What about Liip salary principles?

Liip salary principles are in line with the values and the vision of our company. Salaries, gratification and allowances (double family allowance, paternity leave of four weeks, to name just two) are considered as an overall compensation package. Aiming at a fair and equal internal distribution, to be in line with the personnel market value and to be company-performance oriented.

  • We pay a gratification to all or to none (no individual bonus).
  • We do not negotiate salaries.
  • We maintain a very low wage ratio of 2.5 (ratio between the highest and lowest salary).
  • We don’t discriminate between job type.

How does Liip salary system work ?

Liipers are evaluated on six criteria. Based on the number of points, they rank a salary level given a formula (some criteria are weighted more and some are caped). The salary criteria we use are: education, work experience, expertise, teamplayer/motivator, communication and responsibility/entrepreneurship. While some ”hard facts” like the highest diploma are easy to map to point values, soft criteria are more difficult to evaluate.

Examples:

A short description of the image

That’s why peer evaluation is very important: Once a year, Liipers select five colleagues to evaluate their salaries. Those colleagues decide (and explain), if the current evaluation should be kept or put on a higher/lower level, for each criteria. At the so-called yearly salary conference, salary points and salaries are re-evaluated. Decisions on salary adaptations are taken during that conference. The role Salary Determiner takes the peer evaluation results and a annually company-wide feedback survey into account, discusses them and makes sure equality across the whole company.
Additionally, we apply always the four-eyes principle when salaries are set or changed. And of course, the Salary Determiner role is multi-filled with Liipers of various locations and with different professional profiles.

How does this system help to improve salary equality?

We don’t negotiate salaries. New Liipers salaries are set within the same system and evolve in time and based on experience and feedback rounds. Everything about salary is transparent: the system
and the point values. Such transparency helps to avoid inequality.

No salary system will be considered 100% fair by every employee, but companies can do a lot to make it more understandable, transparent and equal. We will continue our efforts in this direction and share them here. Stay tuned!

]]>