Multiple choices in a list with compose

Multiple choices in a list with compose

Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.

Mutliple choices in a list with compose

you have a list with compose it is easier and if you want to do multiple choices in a list with compose you have to use rememberSaveable.

When you use compose you have to set up the gradl like this:

kotlinOptions {
jvmTarget = ‘1.8’
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion “1.0.0-beta08”
}
}

dependencies {

implementation ‘androidx.core:core-ktx:1.5.0’
implementation ‘androidx.appcompat:appcompat:1.3.0’
implementation ‘com.google.android.material:material:1.3.0’
implementation ‘androidx.constraintlayout:constraintlayout:2.0.4’
testImplementation ‘junit:junit:4.13.2’
androidTestImplementation ‘androidx.test.ext:junit:1.1.2’
androidTestImplementation ‘androidx.test.espresso:espresso-core:3.3.0’

// Compose
implementation “org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta08”
implementation “androidx.activity:activity-compose:1.3.0-beta01”
implementation “androidx.compose.runtime:runtime:1.0.0-beta08”
implementation “androidx.compose.ui:ui:1.0.0-beta08”
implementation “androidx.compose.foundation:foundation:1.0.0-beta08”
implementation “androidx.compose.foundation:foundation-layout:1.0.0-beta08”
implementation “androidx.compose.material:material:1.0.0-beta08”
implementation “androidx.compose.runtime:runtime-livedata:1.0.0-beta08”
implementation “androidx.compose.ui:ui-tooling:1.0.0-beta08”
implementation “com.google.android.material:compose-theme-adapter:1.0.0-beta08”
}
Share

While remember helps you retain state across recompositions, the state is not retained across configuration changes. For this, you must use rememberSaveable. rememberSaveable automatically saves any value that can be saved in a Bundle. For other values, you can pass in a custom saver object.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Surface
import androidx.compose.material.TabRowDefaults.Divider
import androidx.compose.material.Text
import androidx.compose.material.Checkbox
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp {
                MyScreenContent()
            }
        }
    }
}

@Composable
fun MyApp(content: @Composable () -> Unit) {
    Surface(color = Color.White) {
        content()
    }
}

@Composable
fun MyScreenContent(
    names: List<String> = List(15) { "Item #$it" },
    completed: MutableList<Boolean> = MutableList(15) { false }
) {
    Column(modifier = Modifier.fillMaxHeight()) {
        NameList(names, Modifier.weight(1f), completed)
    }
}

@Composable
fun NameList(names: List<String>, modifier: Modifier, completed: MutableList<Boolean>) {
    LazyColumn(modifier = modifier) {
        itemsIndexed(items = names) { index: Int, item: String ->
            Greeting(name = item, completed, index)
            Divider(color = Color.Black)
        }
    }
}

@Composable
fun Greeting(name: String, completedList: MutableList<Boolean>, index: Int) {
    var isSelected by rememberSaveable { mutableStateOf(completedList[index]) }
    val backgroundColor by animateColorAsState(if (isSelected) Color.Blue else Color.Transparent)

    Row {
        Text(
            text = "Hello $name!",
            modifier = Modifier
                .padding(24.dp)
        )
        Checkbox(
                checked = isSelected,
                onCheckedChange = {
                    isSelected = !isSelected
                    completedList[index] = !isSelected
                },
                modifier = Modifier
                    .background(color = backgroundColor)
            )
        }
    }


@Preview("MyScreen preview")
@Composable
fun DefaultPreview() {
    MyApp {
        MyScreenContent()
    }
}

you can download in Github

How to use GraphQL with Retrofit on Android?

How to use GraphQL with Retrofit on Android?

Have you use GraphQL on Android?

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

Apollo Android is a GraphQL client that generates Java and Kotlin models from GraphQL queries. These models give you a type-safe API to work with GraphQL servers. Apollo helps you keep your GraphQL query statements together, organized, and easy to access.

You can choose to use Apollo in android when you query is static. But what happen when the query is dynamic query?

We recommend you to use GraphQL with retrofit.

In your manifest to add

<uses-permission android:name="android.permission.INTERNET"/>

Your dependencies

// Kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4'

//OkHttp
implementation ("com.squareup.okhttp3:okhttp:3.12.12"){
  force = true //API 19 support
}
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.12'

//retrofit
implementation "com.squareup.retrofit2:retrofit:2.7.1"
implementation "com.squareup.retrofit2:converter-scalars:$2.7.1"

Also Java 8 compatibility

android {

    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

With the service

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface GraphQLService {

@Headers("Content-Type: application/json")
@POST("/")
suspend fun postDynamicQuery(@Body body: String): Response
}

you can create a object

import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory

object GraphQLInstance {

    private const val BASE_URL: String = "http://192.155.1.55:2000/"

    val graphQLService: GraphQLService by lazy {
        Retrofit
            .Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(GraphQLService::class.java)
    }
}

In the activity you can create this method

private fun post(userId: String){
    val retrofit = GraphQLInstance.graphQLService
    val paramObject = JSONObject()
    paramObject.put("query", "query {users(userid:$userId){username}}")
    GlobalScope.launch {
        try {
            val response = retrofit.postDynamicQuery(paramObject.toString())
            Log.e("response", response.body().toString())
        }catch (e: java.lang.Exception){
            e.printStackTrace()
        }
    }
}

You can check GraphQL with Retrofit on Android in the example in GitHub
GraphQL with Retrofit on Android?

Dagger in kotlin

Dagger in Kotlin

In object oriented design, the dependency inversion principle is a specific form of decoupling software modules.
Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
The idea behind points A and B of this principle is that when designing the interaction between a high-level module and a low-level one, the interaction should be thought of as an abstract interaction between them. This not only has implications on the design of the high-level module, but also on the low-level one: the low-level one should be designed with the interaction in mind and it may be necessary to change its usage interface.

Dagger in kotlin
In Android we have Dagger is tool to do dependency inversion.

You can see a example in GitHub

We learn with a simple dagger example in Kotlin:

We have a car

class Car() {

    lateinit var engine: Engine
    lateinit var wheels: Wheels

    @Inject
    constructor(
        engine: Engine,
        wheels: Wheels
    ) : this() {
        this.engine = engine
        this.wheels = wheels

    }

    fun drive():String {
        return "driving..."
    }
}

We can build this car with engine

class Engine @Inject
internal constructor()

and wheels

class [email protected]
internal constructor()

With this to object We can create a object of car. To create a object in Dagger we have to create a interface component:

@Component
interface CarComponent {

    var car:Car
}

And now in our MainActivity, We can use our object:

class MainActivity : AppCompatActivity() {

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

        val carComponent:CarComponent = DaggerCarComponent.create()

        text_view.text = carComponent.car.drive()

    }
}

You can see a example in GitHub

Kotlin Android Extensions in Adapter

Kotlin Android Extensions in Adapter

Sometimes is difficult to use kotlin android extensions, the best way to use Kotlin synthetic in Adapter or ViewHolder is like this:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = tasks[position]
holder.bind(item, checkTask, deleteTask)
}

No need to write

val tvTask = view.findViewById(R.id.tvTask)
tvTask.text = task.description

Just

 
itemView.task_item_textView.text = task.description

Or in fun bind you can put

 = with(itemView)

All the fun

fun bind(task: Task, checkTask: (Task) -> Unit, deleteTask: 
(Task) -> Unit) = with(itemView){
            task_item_textView.text = task.description
            task_item_done_checkBox.isChecked = task.completed
            task_item_done_checkBox.setOnClickListener{checkTask(task)}
            setOnClickListener { deleteTask(task) }
        }

Example in Git Hub

notifyDataSetChanged() – Android Example [Updated]

notifyDataSetChanged() example:

This android function notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.

You can use notifyDataSetChanged ArrayAdapter, but it only works if you use the add(), insert(), remove(), and clear() on the Adapter.

When an ArrayAdapter or BaseAdapter are constructed, it holds the reference for the List that was passed in. If you were to pass in a List that was a member of an Activity, and change that Activity member later, the ArrayAdapter is still holding a reference to the original List. The Adapter does not know you changed the List in the Activity.

How to implement notifyDataSetChanged

Your choices are:

Use the functions of the ArrayAdapter to modify the underlying List (add(), insert(), remove(), clear(), etc.)
Re-create the ArrayAdapter with the new List data. (Uses a lot of resources and garbage collection.)
Create your own class derived from BaseAdapter and ListAdapter that allows changing of the underlying List data structure.
Use the notifyDataSetChanged() every time the list is updated. To call it on the UI-Thread, use the runOnUiThread() of Activity. Then, notifyDataSetChanged() will work.

Multiple choice in listView
You need:

Interface
Data (String text, boolean selected)
Adapter
Fragment or Activity (calling notifyDataSetChanged custom adapter)
list_item.xml

You can start with data:

public class MultipleData {
    private String text;
    private boolean selected;

    public MultipleData(String text, boolean selected) {
        this.text = text;
        this.selected = selected;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}

list_item.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/list_item_text"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:text="@string/app_name"
        android:layout_toLeftOf="@+id/list_item_check_button"
        android:gravity="center"/>

    <RadioButton
        android:id="@+id/list_item_check_button"
        android:layout_width="wrap_content"
        android:layout_height="90dp"
        android:layout_alignParentRight="true"
        android:checked="false"
        android:clickable="false"
        android:focusable="false" />

</RelativeLayout>

activity_mail.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <ListView
            android:id="@+id/fragment_multiple_list_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

</RelativeLayout>

Adapter

public class MultipleAdapter extends BaseAdapter implements SelectedIndex {

    private final Context mContext;
    List&amp;lt;MultipleData&amp;gt; mList;

    @Override
    public void setSelectedIndex(int position) {

        if (mList.get(position).isSelected()) {
            mList.get(position).setSelected(false);
        } else {
            mList.get(position).setSelected(true);
        }
    }

    static class ViewHolder {
        TextView mTextView;
        RadioButton mRadioButton;
    }

    public MultipleAdapter(Context context, List&amp;lt;MultipleData&amp;gt; list) {
        this.mContext = context;
        this.mList = list;
    }

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

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;

        if (rowView == null) {
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            rowView = inflater.inflate(R.layout.list_item, null);

            ViewHolder viewHolder = new ViewHolder();
            viewHolder.mTextView = (TextView) rowView.findViewById(R.id.list_item_text);
            viewHolder.mRadioButton = (RadioButton) rowView.findViewById(R.id.list_item_check_button);

            rowView.setTag(viewHolder);
        }

        // fill data
        ViewHolder holder = (ViewHolder) rowView.getTag();
        holder.mTextView.setText(mList.get(position).getText());
        holder.mRadioButton.setChecked(mList.get(position).isSelected());

        return rowView;
    }

} 

Interface

public interface SelectedIndex {
    void setSelectedIndex(int position);
}

In your fragment or activity:

public class MultipleFragment extends Fragment implements AdapterView.OnItemClickListener {

    MultipleAdapter mAdapter;

    public MultipleFragment() {
        // Required empty public constructor
    }

    public static MultipleFragment newInstance() {
        return new MultipleFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_multiple, container, false);
        ListView listView = (ListView) view.findViewById(R.id.fragment_multiple_list_view);

        MultipleData android = new MultipleData("Android", false);
        MultipleData iPhone = new MultipleData("iPhone", false);
        MultipleData windowsMobile = new MultipleData("WindowsMobile", false);

        MultipleData blackberry = new MultipleData("Blackberry", false);
        MultipleData webOS = new MultipleData("WebOS", false);
        MultipleData ubuntu = new MultipleData("Ubuntu", false);

        MultipleData windows7 = new MultipleData("Windows7", false);
        MultipleData max = new MultipleData("Max OS X", false);
        MultipleData linux = new MultipleData("Linux", false);

        MultipleData os = new MultipleData("OS/2", false);
        MultipleData symbian = new MultipleData("Symbian", false);

        List&amp;lt;MultipleData&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        list.add(0, android);
        list.add(1, iPhone);
        list.add(2, windowsMobile);

        list.add(3, blackberry);
        list.add(4, webOS);
        list.add(5, ubuntu);

        list.add(6, windows7);
        list.add(7, max);
        list.add(8, linux);

        list.add(9, os);
        list.add(10, symbian);

        mAdapter = new MultipleAdapter(getActivity(),list);
        listView.setAdapter(mAdapter);
        listView.setOnItemClickListener(this);
        return view;
    }

    @Override
    public void onItemClick(AdapterView&amp;lt;?&amp;gt; adapterView, View view, int position, long l) {
        mAdapter.setSelectedIndex(position);
        mAdapter.notifyDataSetChanged();
    }
}

You can download the source code of this example in github.

If you have any questions about the usage of notifyDataSetChanged leave a comment below and we’ll try to help you.

Happy coding!