ASP.NET Core 2.1: Setting Cookies in GDPR Times

Just a quick heads-up to everyone who wonders why adding cookies suddenly stopped working in ASP.NET Core 2.1: it’s not a bug – it’s a feature!

GDPR (the EU’s General Data Protection Regulation) requires the user’s consent even to set a cookie and ASP.NET Core 2.1 tries to handle that for you. That’s what creates a header like this when running a new website from Visual Studio:

GDPR header

And unless the user clicks Accept you won’t be able to store any cookies by default – Response.Cookies.Append("key", "value") will silently fail. Which of course makes sense.

In my case the cookie was essential for the site to work and that’s also being handled: Response.Cookies.Append("key", "value", new CookieOptions { IsEssential = true}) will make the cookie stick even if the user hasn’t clicked Accept.

That doesn’t make the GDPR go away of course and in your own interest you should be honest when determining if a cookie is essential.

Additional Resources

Advertisements
Posted in Uncategorized | Tagged , | Leave a comment

Azure Web Apps: How to Manage Storage Account Credentials in Development and Production

Scenario: your web app – that’s an Azure Function app, a Web API app or a website – uses Azure Blob, Queue or Table storage. That usually means running on the Azure Storage Emulator for local tests and on the real thing in production. How do you automatically switch credentials? An additional requirement usually is to keep the production credentials a secret. That rules out adding them to a config file which may end up in the repository or being copied unknowingly.

The usual approach is to use environment variables. Either a single one containing the connection string or one for the account name and one for its key. Because I didn’t find an example listing all the steps but a lot of traps you get this post. It also gives a few tips on how to publish a web app.

The storage account in Azure is a prerequisite and of course you need an Azure account for that.

Create an Environment Variable on Your Local Windows Machine

The Azure Storage Emulator has a hard-coded connection string and account credentials. That’s why CloudStorageAccount cloudStorageAccount = CloudStorageAccount.DevelopmentStorageAccount; works. This step will make the connection string available via an environment variable.

Because the environment variable will be for the machine and not just for the current user you need to open a command prompt as an administrator. Enter and execute

setx STORAGE_CONNECTION_STRING "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;" /M</code>

If you want to check the value with echo %STORAGE_CONNECTION_STRING% despite the success message you’ll need to open a new command prompt. For the same reason close Visual Studio so it picks up the new environment variable!

Use the Environment Variable in Code

The code to create a CloudStorageAccount instance from the STORAGE_CONNECTION_STRING environment variable looks like this:

string storageConnectionString = Environment.GetEnvironmentVariable("STORAGE_CONNECTION_STRING");
CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(storageConnectionString);

The connection string defines endpoints for Azure Blob, Queue and Table storage – you can access everything with the resulting cloudStorageAccount.

Publish your Web App to Azure

It’s necessary to publish the web app even if it won’t run yet. In Visual Studio just right-click the respective project and click “Publish”. Once you specified your Azure account in the resulting wizard window it will figure out your subscription.

If you publish the first app you obviously have to leave Create New checked and click Publish. On the next page edit the various names until you are comfortable with them because some are hard to change afterwards. The Hosting Plan determines the cost. I’d stick with the default of S1 and see if you can get by with fewer resources and cost later. Especially if you are in your 30 days free trial period.

When publishing additional apps you can select Use Existing instead. Then the most important question is whether to use a single or dedicated service plans. Each service plan comes with additional resources but also with a price tag. Unless you expect to be throttled in a way I’d suggest to start with a single resource plan. Once everything is running check whether you have outgrown it.

Create an Environment Variable for the Web App in Azure
The first step is to copy the storage account’s connection string. For that log into the Azure Portal and

  1. click “Storage accounts” in the list on the left
  2. click the respective storage account in the storage blade on the right
  3. click Access keys in the center
  4. under key1 click the icon right to the key’s Connection string field to copy its content

After that navigate to the app’s settings:

  1. click “All Resources” in the list on the left
  2. click the respective App Service entry in the All resources blade
  3. click Application settings in the App service’s blade – if your app is an Azure Function app make shure to not use an individual function’s settings!
  4. Scroll down to Application Settings and click Ad new setting
  5. paste the value you just copied in the value field and “STORAGE_CONNECTION_STRING” in the key field
  6. save the changes

That’s it: your web app should now work in production, too. You’ll have to repeat creating this environment variable for each web app you publish. Once you have copied the connection string you can just add the setting when in Visual Studio’s publishing wizzard though.

Bonus Tips

The previously explained idea of using the connection string and therefore a single environment variable creates the least amount of work. And if for some reason you chose to use one environment variable for the account name and one for its key you’ll run into additional difficulties. The code will now like this:

string storageAccountName = Environment.GetEnvironmentVariable("AZURE_STORAGE_ACCOUNT", EnvironmentVariableTarget.Process);
string storageAccountKey = Environment.GetEnvironmentVariable("AZURE_STORAGE_ACCESS_KEY", EnvironmentVariableTarget.Process);
StorageCredentials storageCredentials = new StorageCredentials(storageAccountName, storageAccountKey);
cloudStorageAccount = new CloudStorageAccount(storageCredentials, useHttps: true);

But running that will result in an error:
The remote server returned an error: (403) Forbidden. HTTP Status Code: 403 - HTTP Error Message: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature

That’s because the Azure Storage Emulator uses custom endpoints like and specifying account name and key will create a connection string with default endpoints.

To get around that problem you could just use CloudStorageAccount cloudStorageAccount = CloudStorageAccount.DevelopmentStorageAccount; in development mode. Question: how do you tell development from other stages? By sheer coincidence that can be determined by the existence of another environment variable:

bool isLocal = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID"));

Additional Resources

Posted in Uncategorized | Tagged , | Leave a comment

ASP.NET Razor Pages: Post Back List Changes with Page Model Binding

This post shows how to post back changes made to lists used in model binding for ASP.NET Razor pages. Success is dead easy once you figured it out but it took me a while and most examples aren’t complete or don’t use tag helpers.

The demo project is based on the default Web Application template (a.k.a. Razor pages) for .NET Core Web Applications in .NET Core 2.1 RC1.

This listing shows Index.cshtml.cs:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorListChangePostbackDemo.Pages
{
    public class Customer
    {
        public string CustomerID { get; set; }
        public string CustomerName { get; set; }

        // Mandatory default constructor
        public Customer()
        {
        }

        public Customer(string customerID, string customerName)
        {
            this.CustomerID = customerID;
            this.CustomerName = customerName;
        }
    }

    public class IndexModel : PageModel
    {
        // Must be IList, not IEnumerable
        public IList Customers;

        public void OnGet()
        {
            Customers = new List();
            Customers.Add(new Customer("ALFKI", "Alfreds Futterkiste"));
            Customers.Add(new Customer("ANATR", "Ana Trujillo Emparedados y helados"));
        }

        public ActionResult OnPost(IList customers, int index)
        {
            TempData["LastChange"] = $"{customers[index].CustomerID}: {customers[index].CustomerName}";
            return RedirectToPage();
        }
    }
}

Important point #1 (line 27): the model binding provides Customers via an IList because the view needs to use an index – more on that later.

Important point #2 (line 36): OnPost() uses the model binding’s parameter type and name – not case sensitive though. Also note all the Customer objects are passed back – not just the edited one. If you want only data from the edited one you’re on your own and have to resort to JavaScript for the postback.

Another option is to omit the customers parameter in OnPost() and decorate the model’s Customers property with [BindProperty]. In this case the list is populated with values. Otherwise it’s null when OnPost() is called and only created for HTTP GET requests.

The next listing shows Index.cshtml. Because various tags scared the s***t out of WordPress Index.cshtml became an image:

RazorListChangePostbackDemo01

Index.cshtml

Important point #3 (line 17): the view uses a for loop to list the customers, not a foreach loop. That’s actually the most important point because the input fields have to be created like this – note the index mentioned in both the id and the name attribute:

RazorListChangePostbackDemo02

Excerpt from running page in Chrome Developer Tools

The best way to achieve this is to let the asp-for tag helper do all the work, see line 33 in Index.cshtml.

Important point #4 (line 30 in Index.cshtml): use an input field for every model property you need posted back even if it’s not editable. Otherwise that property will have the type’s default value for every list item in OnPost(). That’s to avoid a giant ViewState like the old days’ Web Forms had for Razor pages.

But what to do if the list still isn’t posted back as expected – for example if the respective parameter in OnPost() is null?

Step #1: set a breakpoint in OnPost(), start the browser’s developer tools, perform an edit and click Save. Now check the form data (in Google Chrome: open Network tab, Headers tab): does it have the expected list values? Otherwise the view has a problem, most probably with the input field’s name.

RazorListChangePostbackDemo03

Checking form data in Chrome Developer Tools

Step #2: if the form data has the expected list values check the PageModel’s postback method – does the list parameter have the correct name and type?

Additional Resources

Posted in Uncategorized | Tagged | Leave a comment

Android Grid Drawing Demo

This short demo shows how to underline a specific row of a RecyclerView controlled by a GridLayoutManager. The code is available at Github under the Apache 2 license.
Android-GridDrawingDemo
The idea is of course that the line follows the grid’s content scrolling up and down. Let’s go through the basic setup first:

  • A RecyclerView provides the UI
  • A DemoItem class (model) with a single getText() method returns a String with the id passed to its constructor
  • A DemoViewHolder class extending RecyclerView.ViewHolder holds a TextView and provides a setText() method to change the TextView’s content
  • A DemoAdapter class extending RecyclerView.Adapter maintains a list of DemoItems. Except from creating the TextView on the fly it contains only boilerplate code.

A MainActivity class plugs everything together in onCreate():

  • create 500 DemoItems and pass them to the DemoAdapter
  • connect RecyclerView and DemoAdapter
  • configure the GridLayoutManager and connect it to the RecyclerView

The result is a grid showing 220 items from a total of 500.

// ...
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Set up DemoAdapter
        List demoItems = createDemoItems(500);
        DemoAdapter demoAdapter = new DemoAdapter(this, demoItems);
        // Set up RecyclerView
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setAdapter(demoAdapter);
        recyclerView.addItemDecoration(new DemoItemDecoration());
        // Set up GridLayoutManager
        int spanCount = 10;
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, spanCount);
        recyclerView.setLayoutManager(gridLayoutManager);
    }

    private List createDemoItems(int amount) {
        List demoItems = new ArrayList();
        for (int i = 0; i < amount; i++)
            demoItems.add(new DemoItem(i));
        return demoItems;
    }
}

Line 14 will be the only addition to the basic setup for drawing a line.

Now let's draw the line. Custom drawing – and spacing – in a RecyclerView is done by extending RecyclerView.ItemDecoration.

package de.dbremes.griddrawingdemo;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.widget.TextView;

public class DemoItemDecoration extends RecyclerView.ItemDecoration {
static final String TAG = DemoItemDecoration.class.getSimpleName();
final Paint mPaint;

public DemoItemDecoration() {
// For better performance don't create mPaint in each onDraw()
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(3);
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
float startX = parent.getPaddingLeft();
float startY = getYPositionOfLine(parent);
float endX = parent.getWidth() – parent.getPaddingRight();
float endY = startY;
c.drawLine(startX, startY, endX, endY, mPaint);
TextView textView = (TextView)parent.getChildAt(150);
// Shows that RecyclerView's position != DemoAdapter's position which is what we
// actually need
Log.d(TAG,"parent.getChildAt(150)).getText()=" + textView.getText());
// Shows that RecyclerView keeps only up to 250 items (10 columns * 22 rows + spare rows)
Log.d(TAG, "parent.getChildCount()=" + parent.getChildCount());
}

private float getYPositionOfLine(RecyclerView parent) {
float result = Float.NaN;
for (int i = 0; i = 150
&& parent.getChildLayoutPosition(parent.getChildAt(i)) < 160) {
result = parent.getChildAt(i).getBottom();
break;
}
}
return result;
}
}

The code in onDraw() finds the row to underline and calculates the line's position from there. The twist is that a RecyclerView recycles its items when they scroll out of view. That means the DemoItems and TextViews are kept but their content is replaced with whatever scrolls into view. The log output shows that there are at most 250 children and textView.getText() of the 150th item returns values between the initial 150 and 420.

So to find an item of the 15th row of DemoItems the code cannot rely on parent.getChildAt() because it's based on the RecyclerView's position. Only a detour over RecyclerView.getChildLayoutPosition() gets the DemoAdapter's position which is what we actually need.

Additional notes:

  • try to keep onDraw() as lean as possible to avoid jitter – in this case even the X position’s calculation could be done in the constructor
  • if you were using a FixedGridLayoutManager with some columns off the screen you couldn’t count on all items of a row being present and advance the loop counter by the number of columns
  • if you try to use GridLayoutManager.findFirstVisibleItemPosition() you’ll find it gets the row wrong occasionally and it may return -1 (NO_POSITION)

Additional Resources

Posted in Uncategorized | Tagged | Leave a comment

ClassNotFoundException when Deploying without Android Studio

Have you ever wondered why you didn’t have to set multiDexEnabled true anymore despite adding almost any library to an Android project will extend it to more than 64K methods?

Well, Android Studio conveniently injected that setting when deploying a debug version of your app. That also means a manual deploy for example via adb install ... will result in an error like this:


java.lang.RuntimeException: Unable to instantiate application ...: java.lang.ClassNotFoundException: Didn't find class "..." on path: DexPathList[zip file "/data/app...apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]

While it’s a ClassNotFoundException the “DexPathList” gives away the true reason. So just add multiDexEnabled true like you always did.

If the next adb install ... results in the same error you fell for the same trap I did: Android Studio doesn’t create the .apk file when building via Build | Make Project. Select Build | Build APK instead.

Additional Resources

Posted in Uncategorized | Leave a comment

Data Binding Android ListViews

Innocent UI – but look behind the mask!

At I/O 2015 Google showed data binding to the Android developing masses. It basically generates the boilerplate code everybody had to type in previously to get data to the UI and back.

But while there are samples for about every detail of the technology there was none (*) that showed how to data bind a ListView – so here’s a step by step recipe.

Note that this post uses Android Studio 3.0 which comes with new defaults to generate code and layouts.

1: Enable Data Binding

The first step to data binding always is to enable it in the module’s build.gradle file:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    dataBinding {
        enabled = true
    }
    defaultConfig {
        // ...
    }
    // ...
}
// ...

2: Create the Data Model Class

A data model is just a container for the data to surface and you probably use this type of class regardless of data binding already. In this simple demo the data won’t change and a POJO will do:

// ...
public class DemoData {
    Date mTimestamp;

    public DemoData() {
        mTimestamp = new Date();
    }

    public String getTimestamp() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return sdf.format(mTimestamp);
    }
}

The only surprise is that getTimestamp() doesn’t return a Date but a String. That’s because the value will be bound to a property of type String and Android does no type conversions. Using the Date would result in LoggedErrorException - Cannot find the setter for attribute 'android:text' with parameter type java.util.Date on android.widget.TextView.

By the way: returning an Integer would result in a befuddling android.content.res.Resources$NotFoundException: String resource ID #0x0. That’s because setText() has an overload which takes an Integer and treats it as a resource ID.

And a production app will probably create the SimpleDateFormat instance only once.

3: Create the List Item Layout

The list item layout defines how each of the ListView’s items should look. When Android Studio 3 generates a new layout resource file it looks like this – finally a ConstraintLayout but no traces of data binding:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout      
    xmlns:android="http://schemas.android.com/apk/res/android"      
    android:layout_width="match_parent"      
    android:layout_height="match_parent">

</android.support.constraint.ConstraintLayout>

Using data binding takes four manual steps – see the result below:

  1. wrap the generated xml in a layout element (lines 2 to 5 and 21)
  2. specify the type of data to bind – in this case the previous section’s data model (lines 6 to 8)
  3. add UI elements (lines 12 to 19)
  4. bind UI elements to data (line 16)
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"      
    xmlns:app="http://schemas.android.com/apk/res-auto"      
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="demoData" type="de.dbremes.listviewdatabinding.DemoData"/>
    </data>
    <android.support.constraint.ConstraintLayout          
        android:layout_width="match_parent"          
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/positionTextView"              
            android:layout_width="wrap_content"                          
            android:layout_height="wrap_content"              
            android:text="@{demoData.timestamp}"              
            app:layout_constraintTop_toBottomOf="parent"              
            app:layout_constraintStart_toStartOf="parent"              
            tools:text="1: 2017-11-05 14:41:36"/>
    </android.support.constraint.ConstraintLayout>
</layout>

The most important part isn’t even shown: this layout is called layout_demo_item.xml and will result in a data binding class named LayoutDemoItemBinding once you compile the project.

Had the data model simply returned a Date you’d have to convert it here: @{String.valueOf(demoData.timestamp)}.

Note that IDs are only used for constraints and state management but not needed for data binding.

4: Create the Layout with the ListView using the List Items

Data binding the ListView itself consists of the same steps as creating the list item layout. This example starts from Android Studio’s Basic Activity template which provides this layout in content_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout      
    xmlns:android="http://schemas.android.com/apk/res/android"      
    xmlns:app="http://schemas.android.com/apk/res-auto"      
    xmlns:tools="http://schemas.android.com/tools"      
    android:layout_width="match_parent"      
    android:layout_height="match_parent"      
    app:layout_behavior="@string/appbar_scrolling_view_behavior"      
    tools:context="de.dbremes.listvewdatabinding.MainActivity"      
    tools:showIn="@layout/activity_main">

    <TextView      android:layout_width="wrap_content"      
        android:layout_height="wrap_content"
        android:text="Hello World!"      
        app:layout_constraintBottom_toBottomOf="parent"      
        app:layout_constraintLeft_toLeftOf="parent"      
        app:layout_constraintRight_toRightOf="parent"      
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Result after completing the required steps:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"      
    xmlns:app="http://schemas.android.com/apk/res-auto"      
    xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout          
        android:layout_width="match_parent"          
        android:layout_height="match_parent"          
        app:layout_behavior="@string/appbar_scrolling_view_behavior"          
        tools:context="de.dbremes.listviewdatabinding.MainActivity"          
        tools:showIn="@layout/activity_main">

        <ListView
            android:id="@+id/demoListView"              
            android:layout_width="match_parent"              
            android:layout_height="match_parent"              
            android:layout_marginLeft="8dp"              
            android:layout_marginRight="8dp"              
            android:layout_marginTop="8dp"              
            app:layout_constraintLeft_toLeftOf="parent"              
            app:layout_constraintTop_toTopOf="parent"              
            tools:listitem="@layout/layout_demo_item"/>

    </android.support.constraint.ConstraintLayout>
</layout>

Note line 23 which shows sample list view items – slick!

5: Create an Adapter Class

An adapter connects data a.k.a. data model and UI. Because a ListView provides the UI a subclass of BaseAdapter is required.

Once you create a class extending BaseAdapter Android Studio will volunteer to complete it with the four methods shown:

package de.dbremes.listviewdatabinding;

import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class DemoAdapter extends BaseAdapter {
    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        return null;
    }
}

The first three methods just need a list of DemoModel instances to work and getView() is where things get interesting. This is the completed class:

public class DemoAdapter extends BaseAdapter {
    private ArrayList<DemoData> mDemoModels;
    private LayoutInflater mLayoutInflater;

    DemoAdapter(ArrayList<DemoData> demoModels) {
        mDemoModels = demoModels;
    } // ctor()

    // @Override
    public int getCount() {
        return mDemoModels.size();
    }

    @Override
    public Object getItem(int i) {
        return mDemoModels.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(final int i, View view, final ViewGroup viewGroup) {
        View result = view;
        LayoutDemoItemBinding binding;
        if (result == null) {
            if (mLayoutInflater == null) {
                mLayoutInflater = (LayoutInflater) viewGroup.getContext()
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            }
            binding = LayoutDemoItemBinding.inflate(
                    mLayoutInflater, viewGroup, false);
            result = binding.getRoot();
            result.setTag(binding);
        }
        else {
            binding = (LayoutDemoItemBinding) result.getTag();
        }
        result.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Context context = viewGroup.getContext();
                Toast toast = Toast.makeText(context,
                        "Position=" + Integer.toString(i) + ": " + ((DemoData)getItem(i)).getTimestamp(),
                        Toast.LENGTH_SHORT);
                toast.show();
            }
        });
        binding.setDemoData(mDemoModels.get(i));
        return result;
    }
}

The constructor just takes and stores the data. Then getView() is called for each of the ListView’s elements:

  1. i provides the element’s position – why should the parameter’s name give that away?
  2. view is a used view representing a list item layout or null. If it’s a view the method just updates it with the respective data.
  3. viewGroup is the parent of the view to return

If getView() doesn’t get a view to reuse the LayoutDemoItemBinding class previously generated from layout_demo_item.xml creates the proper binding. That gets provided with the data and stored in the resulting view’s tag.

Basic data binding is accompanied by showing how to handle click events on items in lines 41 to 50.

6: Connect Adapter Class and ListView

Connecting Adapter Class and ListView is done in MainActivity’s OnCreate() handler:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        ActivityMainBinding binding
                = DataBindingUtil.setContentView(this, R.layout.activity_main);
        final ArrayList<DemoData> demoDataList = new ArrayList<>();
        demoDataList.add(new DemoData());
        final DemoAdapter adapter = new DemoAdapter(demoDataList);
        // This requires
        // - adding a layout element to activity_main.xml
        // - adding an id to the existing include element
        binding.contentMain.demoListView.setAdapter(adapter);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                demoDataList.add(new DemoData());
                adapter.notifyDataSetChanged();;
            }
        });
    }

Lines 4 and 14 to 24 come with the default implementation. After that:

  1. instantiate the binding (line 5, 6)
  2. instantiate the adapter (line 9)
  3. plug it all together (line 13)

Android Studio’s Basic Activity template comes with activity_main.xml which has an include element for content_main.xml. To make data binding in content_main.xml work you have to:

  • wrap activity_main.xml in a layout element like shown for the other layouts
  • add an id attribute to activity_main.xml’s include element for content_main.xml – android:id=”@+id/content_main” was used in this case leading to a binding named contentMain

Additional Resources

 

Posted in Uncategorized | Tagged | Leave a comment

Styling Android Toasts the Easy Way

The first step to style Android Toasts is to define a layout, right? Well often it’s not, because to just show text and an image – yes, the built-in Toast can show an image – you don’t need a layout.

But let’s get another common misconception about Toast layouts out of the way before getting rid of them altogether. That misconception is even in Google’s own sample code which looks like this:

LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
                (ViewGroup) findViewById(R.id.custom_toast_container));

TextView text = (TextView) layout.findViewById(R.id.text);
text.setText("This is a custom toast");

Toast toast = new Toast(getApplicationContext());
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();

The thing is (ViewGroup) findViewById(R.id.custom_toast_container) will always return null because the Activity has no idea of R.id.custom_toast_container.

But just calling it like View layout = inflater.inflate(R.layout.custom_toast, null); causes a Lint warning “Avoid passing null as the view root …”. That’s usually correct because while the inflater will still do its job the configuration information would get lost which may or may not lead to a messed up layout.

This doesn’t apply to Toasts though and passing null is perfectly OK. So just suppress the Lint warning with @SuppressLint("InflateParams").

Now let’s take a look at this code that customizes Android’s built-in toast on-the-fly:

// ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
            @Override
            public void onClick(View view) {
                Toast toast = Toast.makeText(
                        MainActivity.this, "Android loves toasts!", Toast.LENGTH_LONG);
                toast.setGravity(Gravity.CENTER, 0, 0);
                View toastView = toast.getView();
                // You could also add Views to toastView here or scale it
                toastView.setBackgroundColor(0x80FFFFFF);
                // Or keep toast color and just change opacity:
                //toastView.getBackground().setAlpha(128);
                TextView textView = (TextView) toastView.findViewById(android.R.id.message);
                textView.setAlpha(.5f);
                textView.setGravity(Gravity.CENTER_HORIZONTAL);
                textView.setTextSize(40);
                Drawable toastLoveDrawable
                        = ContextCompat.getDrawable(MainActivity.this, R.mipmap.ic_toast_love);
                toastLoveDrawable.setAlpha(128);
                // textView.setCompoundDrawables() doesn't show the drawable!
                textView.setCompoundDrawablesWithIntrinsicBounds(
                        null, toastLoveDrawable, null, null);
                toast.show();
            }
        });
    }
// ...

Its result looks like this – “pretty” was not in the requirements:

Lines 14 to 16 are standard code. But Line 17 shows how to access the built-in Toast’s layout. You can manipulate it like any other View object and in this case its background color and opacity are changed – more on that later.

Lines 22 to 31 show how to style the TextView. The straightforward part is setting its position, opacity and size.

The surprising part is that each TextView contains a means to show an image – certainly a debatable architectural decision but that’s another story. Again the code focuses on setting the opacity.

The code not shown is just Android Studio’s Basic Activity template with some color and text adjustments to get a proper background for showing a transparent Toast.

In lieu of witty closing words here’s how to calculate opacity / alpha values. The alpha value controls a View’s opacity. It’s beween 0 (completely transparent) and 1 (completely opaque). So textView.setAlpha(.5f) results in a half transparent TextView.

A Drawable’s alpha values work the same but are beween 0 and 255. So toastLoveDrawable.setAlpha(128) results in a half transparent Drawable. Because getBackground() returns a Drawable toastView.getBackground().setAlpha(128) works likewise.

A 3rd option to get transparent output is specifying the background color’s alpha channel. Values are beween 0 and 255 again but they are usually given as a hexadecimal number. And 128 equals 0x80 so toastView.setBackgroundColor(0x80FFFFFF) specifies a half-transparent white background.

Additional Resources

Posted in Uncategorized | Tagged | Leave a comment