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` 
Tweet