Friday, 27 September 2019

Post 73: How to create Android app that persistently saves custom objects

In almost any app you need to persistently save custom information in your app and later load it and display it to the user. Unfortunately Android doesn't provide you this functionality out of the box. Therefore in this tutorial I'll show you how to create an app that persistently saves custom objects with gson.
I'll use:

  1. Android studio
  2. android 28
  3. gson
  4. gradle


In it is mainly based on this post:

https://codinginflow.com/tutorials/android/save-arraylist-to-sharedpreferences-with-gson

But because some API has changed (for this have a look here: https://developer.android.com/jetpack/androidx/migrate/class-mappings) since then you won't be able to create an app if you follow the above mentioned tutorial. It took me a while to get it going. In order to save time, I create this tutorial for you. Also I added some additional information that was not mentioned in my reference post and that may be helpful for beginners.

First create a new app via android studio select the empty app template and call it "MyApplication".

Then add the following dependencies into your build.gradle file:

dependencies {
implementation 'com.android.support:design:28.0.+'
implementation 'com.google.code.gson:gson:2.8.5'
}

We need for our UI some designs that is not provided by the standard android SDK. Therefore we have to add the design dependency:
implementation 'com.android.support:design:28.0.+'

Google provides you with gson methods you can use to serialize and deserialize java objects
https://github.com/google/gson

Mind you, the number 28 in i.e. 'com.android.support:design:28.0.+' refers to your target SDK version. make sure that these are always the same number. In my example it's 28. Your's may differ. In order to know which target version you are using, you can also check in your gradle-file:

android {
    compileSdkVersion 28
    defaultConfig {
...
        targetSdkVersion 28
...
    }
}

Once you have added the above dependencies, click on the "sync" (at the top right corner) so gradle can download the needed dependencies.

The next step is to design our main layout. We want two input fields where we type in some data (that we will alter save) and two buttons: Insert and save. The Insert button inserts our newly created object into the list displayed in the main view. The save button saves it persistently on our app. In the "layout" folder write inside the "activity_main.xml" file this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context="de.pandaquests.myapplication.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="100dp"
        android:background="@android:color/darker_gray" />

    <EditText
        android:id="@+id/edittext_line_1"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="52dp"
        android:hint="Line 1" />

    <EditText
        android:id="@+id/edittext_line_2"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:hint="Line 2" />

    <Button
        android:id="@+id/button_insert"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@id/edittext_line_1"
        android:layout_marginStart="13dp"
        android:layout_marginTop="23dp"
        android:layout_toEndOf="@id/edittext_line_1"
        android:text="insert" />

    <Button
        android:id="@+id/button_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@id/button_insert"
        android:layout_toEndOf="@+id/button_insert"
        android:text="save" />

</RelativeLayout>

Next is we create the UI for our custom element. The custom element represents the data we insert into the list. Create in the "layout" folder a file named "example_item.xml" by right clicking and choose new -> Layout resource file. Write "example_item.xml" as the name and ignore the rest, because we will overwrite everything insdie of that file with the following code inside of it:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="4dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="4dp">

        <TextView
            android:id="@+id/textview_line1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Line 1"
            android:textColor="@android:color/black"
            android:textSize="20sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/textview_line_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/textview_line1"
            android:layout_marginStart="8dp"
            android:text="Line 2"
            android:textSize="15sp" />

    </RelativeLayout>

</androidx.cardview.widget.CardView>

Once we are done with that, we will create the needed functionality for our main activity. Inside the "MainActivity.java" file write the following:

package de.pandaquests.myapplication;

import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    ArrayList<ExampleItem> mExampleList;
    private RecyclerView mRecyclerView;
    private ExampleAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        loadData();
        buildRecyclerView();
        setInsertButton();

        Button buttonSave = findViewById(R.id.button_save);
        buttonSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                saveData();
            }
        });
    }

    private void saveData() {
        SharedPreferences sharedPreferences = getSharedPreferences("shared preferences", MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        Gson gson = new Gson();
        String json = gson.toJson(mExampleList);
        editor.putString("task list", json);
        editor.apply();
    }

    private void loadData() {
        SharedPreferences sharedPreferences = getSharedPreferences("shared preferences", MODE_PRIVATE);
        Gson gson = new Gson();
        String json = sharedPreferences.getString("task list", null);
        Type type = new TypeToken<ArrayList<ExampleItem>>() {}.getType();
        mExampleList = gson.fromJson(json, type);

        if (mExampleList == null) {
            mExampleList = new ArrayList<>();
        }
    }

    private void buildRecyclerView() {
        mRecyclerView = findViewById(R.id.recyclerview);
        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(this);
        mAdapter = new ExampleAdapter(mExampleList);

        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);
    }

    private void setInsertButton() {
        Button buttonInsert = findViewById(R.id.button_insert);
        buttonInsert.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EditText line1 = findViewById(R.id.edittext_line_1);
                EditText line2 = findViewById(R.id.edittext_line_2);
                insertItem(line1.getText().toString(), line2.getText().toString());
            }
        });
    }

    private void insertItem(String line1, String line2) {
        mExampleList.add(new ExampleItem(line1, line2));
        mAdapter.notifyItemInserted(mExampleList.size());
    }
}


You'll probably see lots of erros. But don't worry it's because there are still files missing. Next we create the "ExampleAdapter.java". Create in your project folder (the folder where also your MainActiviy.java file is) a file named "ExampleAdapter.java"

package de.pandaquests.myapplication;

import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

public class ExampleAdapter extends RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder> {
    private ArrayList<ExampleItem> mExampleList;

    public static class ExampleViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextViewLine1;
        public TextView mTextViewLine2;

        public ExampleViewHolder(View itemView) {
            super(itemView);
            mTextViewLine1 = itemView.findViewById(R.id.textview_line1);
            mTextViewLine2 = itemView.findViewById(R.id.textview_line_2);
        }
    }

    public ExampleAdapter(ArrayList<ExampleItem> exampleList) {
        mExampleList = exampleList;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.example_item, parent, false);
        ExampleViewHolder evh = new ExampleViewHolder(v);
        return evh;
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        ExampleItem currentItem = mExampleList.get(position);

        holder.mTextViewLine1.setText(currentItem.getLine1());
        holder.mTextViewLine2.setText(currentItem.getLine2());
    }

    @Override
    public int getItemCount() {
        return mExampleList.size();
    }
}

Now we only need our custom Object. Let's create "ExampleItem.java" file in your project folder and paste in the following code:

package de.pandaquests.myapplication;
public class ExampleItem {
    private String mLine1;
    private String mLine2;

    public ExampleItem(String line1, String line2) {
        mLine1 = line1;
        mLine2 = line2;
    }

    public String getLine1() {
        return mLine1;
    }

    public String getLine2() {
        return mLine2;
    }
}

We should now be set. Press on run and you should see a working sample app that saves custom data.

If you like it, then please share it with others.

You can download the app here from my gitHub and tweak it as you like:
https://github.com/pandaquests/AndroidSaveData

Use it as a base for your own projects. If you want, then tell me what projects you intend or have done with. I'm really curious to know. So long

Thursday, 26 September 2019

Post 72: How to create evenly divided buttons that fills the entire space of the viewport on Android apps

I had to try different layout and read through several Stackoverflow posts until I could achieve what I wanted: buttons evenly spread on the viewport. At first I thought you can easily achieve that with a GridLayout. But no, you can achieve this with nested LinearLayout:

<?xml version="1.0" encoding="utf-8"?>
    <!--tools:showIn="@layout/activity_test_widget"-->
<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="test.TodoWidget">

    <LinearLayout
        android:layout_width="368dp"
        android:layout_height="439dp"
        android:orientation="vertical"
        app:layout_constraintVertical_weight="1"
        tools:layout_editor_absoluteY="8dp"
        tools:layout_editor_absoluteX="8dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/editTextTaskInput"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:hint="test"
                android:ems="10"
                android:inputType="textMultiLine" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            app:layout_constraintHorizontal_weight="1"
            android:orientation="horizontal">

            <Button
                android:id="@+id/b1"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="b1" />

            <Button
                android:id="@+id/b2"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="b2" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            app:layout_constraintHorizontal_weight="1"
            android:orientation="horizontal">
            <Button
                android:id="@+id/b3"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="b3" />
            <Button
                android:id="@+id/b4"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="b4" />
        </LinearLayout>
    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/nextButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_media_next"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toEndOf="parent"
        tools:layout_editor_absoluteX="328dp" />

</android.support.constraint.ConstraintLayout>

Sunday, 8 September 2019

Post 71: Publish gitHub page with VueJS + Vue cli


  1. Create a project on gitHub
  2. clone that gitHub project on your computer with `git clone ...`
  3. in your terminal, go to that project with `cd .....`
  4. create a vue project in that folder with `vue create .`
  5. install all dependencies `yarn install`
  6. in the `.gitignore` file comment out the folder `/dist` (we will later publish our vueJS page in that `dist` folder and push it on gitHub)
  7. create a new branch with `git co -b gh-pages`
  8. create a new file `vue.config.js` with `touch vue.config.js`
  9. in that file write: module.exports = { publicPath: ‘<my-project>’ }
  10. modify the changes you want to make
  11. test your changes with `yarn serve`
  12. if you are satisfied we can build for production with `yarn build`
  13. add the changes you want to push: `git add dist && git commit -m "Initial dist subtree commit`
  14. push only the dist folder to your gh-branch on gitHub: `git subtree push --prefix dist origin gh-pages`. This part is very important. If you push everything into gitHub, then nothing will be displayed. The trick is to only push the `dist` folder on to gitHub. In order not to lose any changes, I would commit and push everything to `master` and on the branch `gh-pages`, I'd only push the `dist` changes  
  15. go to gitHub and then to the settings
  16. in the section gitHub pages you should see a link where your vueJS can be reached. Click on that and you should see the same page you see earlier with `yarn serve` 

Tuesday, 27 August 2019

Post 70: Remove blue background on form input fields in Chrome

Chrome adds a default blue background color in your form input fields, e.g. text fields or password input fields.

In order to remove it apply this styling

<style>  input:-webkit-autofill,  input:-webkit-autofill:hover,  input:-webkit-autofill:focus textarea:-webkit-autofill,  textarea:-webkit-autofill:hover textarea:-webkit-autofill:focus,  select:-webkit-autofill,  select:-webkit-autofill:hover,  select:-webkit-autofill:focus {
    transition: background-color 5500s ease-in-out 0s;  }
</style>

Monday, 10 December 2018

Post 69: Git (merge|diff) tools and aliases

# based on http://blogs.pdmlab.com/alexander.zeitler/articles/installing-and-configuring-p4merge-for-git-on-ubuntu/



$ cd ~/Downloads

$ wget https://cdist2.perforce.com/perforce/r18.2/bin.linux26x86_64/p4v.tgz

$ tar zxvf p4v.tgz

$ sudo mkdir /opt/p4v

$ cd p4v-2018.2.1666551 # or the current version

$ sudo mv * /opt/p4v

$ sudo ln -s /opt/p4v/bin/p4merge /usr/local/bin/p4merge

vim ~/.gitconfig

[color]

ui = true

diff = true

branch = auto

status = auto

[alias]

new = checkout -b

co = checkout

ci = commit

cm = commit -m

cam = commit -am

ca = commit --amend # careful

st = status

br = branch

lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen--> %cr%Creset by %Cblue%cN <%cE>%Creset' --abbre

v-commit --date=relative

hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short

type = cat-file -t

dump = cat-file -p

s = status --short

a = !git add . && git status

au = !git add -u . && git statustus

aa = !git add . && git add -u . && git status

ac = !git add . && git commit

acm = !git add . && git commit -m

put = push origin HEAD

get = pull origin HEAD

[merge]

keepBackup = false;

tool = p4merge

[mergetool]

prompt = false

[mergetool "p4merge"]

cmd = p4merge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"

keepTemporaries = false

trustExitCode = false

keepBackup = false

[diff]

tool = p4merge

[difftool]

prompt = false

[difftool "p4merge"]

cmd = p4merge "$LOCAL" "$REMOTE"

keepTemporaries = false

trustExitCode = false

keepBackup = false

Tuesday, 20 November 2018

Post 68: Webassembly example

Lately I wanted to dive into Web Assembly. The examples I found on the internet wouldn't work or information was missing. I would spend a couple of hours to figure it out. In order to save yourself time, I created this post.

I was inpsired by this post (but the project wouldn't run for me): https://medium.freecodecamp.org/get-started-with-webassembly-using-only-14-lines-of-javascript-b37b6aaca1e4#--respond

The file structure will look like this:
wasmDemo
|_index.html
|_scripts.js
|_server.js
|_squarer.wasm

So, everything is in one directory with now subfolder.

First go to the following page in order to write a C++ program:
https://mbebenita.github.io/WasmExplorer/

Let's write a squarer program:

int squarer(int i) {
  return i * i;
}

Paste that into the site above. Then compile it and download the `.wasm` file. Move that file into our folder "wasmDemo".

Next create a html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>WASM Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>WASM Demo</h1>
<script src="./scripts.js"></script>
</body>
</html>

Then create a js file:
let squarer;

function loadWebAssembly(fileName) {
return fetch(fileName)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
return new WebAssembly.Instance(module);
});
}
loadWebAssembly('./squarer.wasm').then(instance => {
squarer = instance.exports._Z7squareri;
console.log('Finished compiling! Ready when you are...');
});

Since we have to run it on the server, let's create a server.js file for our node server:
var http = require('http');
var fs = require('fs');
var path = require('path');

const PORT = 8080;

http
.createServer(function(request, response) {
var filePath = '.' + request.url;
if (filePath == './') {
filePath = './index.html';
}

var extname = String(path.extname(filePath)).toLowerCase();
var mimeTypes = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.wasm': 'application/wasm'
};

var contentType = mimeTypes[extname] || 'application/octet-stream';

fs.readFile(filePath, function(error, content) {
if (error) {
response.writeHead(404);
response.end('404 Not Found');
} else {
response.writeHead(200, { 'Content-Type': contentType });
response.end(content, 'utf-8');
}
});
})
.listen(PORT);

If everything is correct, then start the application with `node server.js`

Now you can access the squarer function, that we wrote in C++ also in the js file or in the console.

Wednesday, 24 October 2018

Post 67: Set background image in ubuntu 18.04

Type in your terminal:
sudo gedit /usr/share/gnome-shell/theme/ubuntu.css
Then search for "lockDialogGroup". Replace the CSS query with the following:
#lockDialogGroup {
background: #2c001e url(file:///[fileLocation/filename.png]);
background-repeat: no-repeat;
background-size: cover;
background-position: center;
}
Tweet