Проблема с UUID, программа выдает исключение NullPointerException

#java #android

#java #Android

Вопрос:

Пожалуйста, не помечайте его как дубликат, я пытался следовать ответам на другие вопросы, но это не помогло.

У меня проблема с приложением, которое я создал в intellij в соответствии с руководством, предоставленным моим профессором (да, я попросил о помощи, он не смог мне помочь). Я сделал все в соответствии с руководством (или я так думаю), но когда я пытаюсь запустить приложение, я получаю это сообщение в моем Logcat:

 2020-11-30 17:50:05.577 9262-9262/com.example.todoapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.todoapp, PID: 9262
    java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.todoapp.Task.getName()' on a null object reference
        at com.example.todoapp.TaskFragment.onCreateView(TaskFragment.java:54)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2600)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:881)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
        at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
        at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
        at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2663)
        at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2613)
        at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:246)
        at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:542)
        at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
        at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
        at android.app.Activity.performStart(Activity.java:8024)
        at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3475)
        at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
        at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
 

Из этого сообщения я понимаю, что существует проблема с Task().getName, но я не знаю, почему оно должно быть нулевым. Вот мои действия: MainActivity.java

 package com.example.todoapp;

import androidx.fragment.app.Fragment;
import android.os.Bundle;
import java.util.UUID;

public class MainActivity extends SingleFragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected Fragment createFragment() {
        UUID taskId = (UUID) getIntent().getSerializableExtra(TaskListFragment.KEY_EXTRA_TASK_ID);
        return TaskFragment.newInstance(taskId);
    }
}
 

SingleFragmentActivity.java:

 package com.example.todoapp;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;

public abstract class SingleFragmentActivity extends AppCompatActivity {

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

        FragmentManager fragmentManager = getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);

        if(fragment == null) {
            fragment = createFragment();
            fragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }
    }
    protected abstract Fragment createFragment();
}
 

TaskListActivity.java:

 package com.example.todoapp;

import android.os.Bundle;

import androidx.fragment.app.Fragment;

public class TaskListActivity extends SingleFragmentActivity {

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

    @Override
    public Fragment createFragment() {
        return new TaskListFragment();
    }
}
 

And here are my classes: Task.java

 package com.example.todoapp;

import java.util.Date;
import java.util.UUID;

public class Task {

    private UUID id;
    private String name;
    private Date date;
    private boolean done;

    public Task() {
        id = UUID.randomUUID();
        date = new Date();
    }

    public UUID getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getDate() {
        return date;
    }


    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }
}
 

TaskFragment.java:

 package com.example.todoapp;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.util.UUID;

public class TaskFragment extends Fragment {

    private EditText nameField;
    private Button dateButton;
    private CheckBox doneCheckBox;
    private Task task;
    private static final String ARG_TASK_ID = "ARG_TASK_ID";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        UUID taskId = (UUID) getArguments().getSerializable(ARG_TASK_ID);
        task = TaskStorage.getInstance().getTaskById(taskId);
    }

    public static TaskFragment newInstance(UUID taskId) {
        Bundle bundle = new Bundle();
        bundle.putSerializable(ARG_TASK_ID, taskId);
        TaskFragment taskFragment = new TaskFragment();
        taskFragment.setArguments(bundle);
        return taskFragment;
    }

    @NonNull
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater,container,savedInstanceState);

        View view = inflater.inflate(R.layout.fragment_task, container, false);
        nameField = view.findViewById(R.id.task_name);
        dateButton = view.findViewById(R.id.task_date);
        doneCheckBox = view.findViewById(R.id.task_done);

        nameField.setText(task.getName());

        nameField.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                task.setName(charSequence.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        dateButton.setText(task.getDate().toString());
        dateButton.setEnabled(false);

        doneCheckBox.setChecked(task.isDone());
        doneCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                task.setDone(isChecked);
            }
        });

        return view;

    }



}
 

TaskLIstFragment.java:

 package com.example.todoapp;

import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class TaskListFragment extends Fragment {

    public static final String KEY_EXTRA_TASK_ID = "KEY_EXTRA_TASK_ID";
    private TaskAdapter adapter;
    private RecyclerView recyclerView;

    private void updateView() {
        TaskStorage taskStorage = TaskStorage.getInstance();
        List<Task> tasks = taskStorage.getTaskList();

        if (adapter == null) {
            adapter = new TaskAdapter(tasks);
            recyclerView.setAdapter(adapter);
        } else {
            adapter.notifyDataSetChanged();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        updateView();
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_task_list, container, false);
        recyclerView = view.findViewById(R.id.task_recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        updateView();

        return view;
    }

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

    private class TaskHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private TextView nameTextView;
        private TextView dateTextView;
        private Task task;
        private final LinearLayout itemElement = itemView.findViewById(R.id.item_element);
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(getActivity(), MainActivity.class);
            intent.putExtra(KEY_EXTRA_TASK_ID, task.getId());
            startActivity(intent);
        }

        public void bind(final Task task) {
            this.task = task;
            itemElement.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(getActivity(), MainActivity.class);
                    intent.putExtra(KEY_EXTRA_TASK_ID, task.getId());
                    startActivity(intent);
                }
            });
            nameTextView.setText(task.getName());
            dateTextView.setText(task.getDate().toString());
        }

        public TaskHolder(LayoutInflater inflater, ViewGroup parent) {
            super(inflater.inflate(R.layout.list_item_task, parent, false));
            itemView.setOnClickListener(this);
            nameTextView = itemView.findViewById(R.id.task_item_name);
            dateTextView = itemView.findViewById(R.id.task_item_date);
        }
    }

    private class TaskAdapter extends RecyclerView.Adapter<TaskHolder> {

        private List<Task> tasks;

        public TaskAdapter(List<Task> tasks) {
            this.tasks = tasks;
        }

        @NonNull
        @Override
        public TaskHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
            return new TaskHolder(layoutInflater, parent);
        }

        @Override
        public void onBindViewHolder(@NonNull TaskHolder holder, int position) {
            Task task = tasks.get(position);
            holder.bind(task);
        }

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

TaskStorage.java:

 package com.example.todoapp;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TaskStorage {
    private static final TaskStorage ourInstance = new TaskStorage();

    public static TaskStorage getInstance() {
        return ourInstance;
    }

    private List<Task> taskList;

    private TaskStorage() {
        taskList = new ArrayList<Task>();
        for(int i=0; i<100; i  ) {
            Task tmp = new Task();
            tmp.setName("Task nr "   i);
            taskList.add(tmp);
        }
    }

    public List<Task> getTaskList() {
        return taskList;
    }

    public Task getTaskById(UUID id) {
        for(Task task:taskList) {
            if(task.getId().equals(id)) return task;
        }
        return null;
    }


}
 

Я думаю, что проблема связана с UUID. Он выдает null, я не знаю почему. Я был бы очень признателен за помощь, поскольку я не знаю, что делать. Любая помощь будет оценена.

Я попытался изменить

 @Override
 public void afterTextChanged(Editable s) {

}
 

Для:

 @Override
public void afterTextChanged(Editable s) {
    nameField.setText(task.getName());
}
 

Я также попытался инициализировать name в конструкторе задачи, но ошибка все еще была там.

Комментарии:

1. «… существует проблема с Task().getName, но я не знаю, почему оно должно быть нулевым». Это неправильный вопрос. 🙂 Ваша задача — сделать его не нулевым, прежде чем использовать его. Итак, правильный вопрос: «Почему это не должно быть null?». Единственная причина, которую я вижу в приведенном коде, это task = TaskStorage.getInstance().getTaskById(taskId); . Так что же это за загадка TaskStorage и что заставляет нас предполагать, что существуют какие-либо задачи с данным taskId ? Похоже, единственное место в коде, которое может помещать в него какие-либо задачи, — это конструктор : private TaskStorage() {...} . Я бы сосредоточился на этом. Вызывается ли оно?

2. Я не внимательно следил за кодом, но каждой задаче присваивается случайный идентификатор. Указан ли правильный идентификатор при попытке найти задачу? Я бы тоже сосредоточился на этом.

Ответ №1:

В вашем TaskFragment :

Либо метод onCreate не вызывался ранее onCreateView (поэтому переменная экземпляра task никогда не назначалась), либо TaskStorage.getInstance().getTaskById(taskId); просто возвращает null, потому что для данного идентификатора нет задачи — и вы активно возвращаете null в таких случаях.

Я не могу судить о вызове методов жизненного цикла, поскольку я не занимаюсь разработкой приложений, но я думаю, что это, вероятно, то, что знает ваш профессор.

Поэтому я думаю, что для данного идентификатора просто нет задачи.

Вы можете избежать NullPointerException , вернув фиктивную задачу, например:

     public Task getTaskById(UUID id) {
        for(Task task:taskList) {
            if(task.getId().equals(id)) return task;
        }
        return new Task();
    }
 

Но это, скорее всего, не решит вашу проблему, просто переместив ее в другое место.

Я думаю, что более чистым способом было бы либо создать исключение, когда задача не может быть найдена, либо вернуть необязательный параметр и обработать его должным образом:

     public Optional<Task> getTaskById(UUID id) {
        for(Task task:taskList) {
            if(task.getId().equals(id)) return Optional.of(task);
        }
        return Optional.empty();
    }

/// or:
    public Task getTaskById(UUID id) {
        for(Task task:taskList) {
            if(task.getId().equals(id)) return task;
        }
        throw new IllegalArgumentException(String.format("There is no task for id '%s'", id));
    }
 

Я бы лично предпочел необязательный вариант, поскольку для вызывающих методы ясно, что существует вероятность возврата пустого необязательного значения.