Прежде чем объяснять, я покажу вам свой (укороченный) код:
public class MainActivity extends AppCompatActivity
{
FrameLayout simpleFrameLayout;
TabLayout tabLayout;
SecondFragment s = new SecondFragment();
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
simpleFrameLayout = (FrameLayout) findViewById(R.id.simpleFrameLayout);
tabLayout = (TabLayout) findViewById(R.id.simpleTabLayout);
// Create a new Tab named "First"
TabLayout.Tab firstTab = tabLayout.newTab();
firstTab.setText("First"); // set the Text for the first Tab
// first tab
tabLayout.addTab(firstTab); // add the tab at in the TabLayout
// Create a new Tab named "Second"
TabLayout.Tab secondTab = tabLayout.newTab();
secondTab.setText("Second"); // set the Text for the second Tab
tabLayout.addTab(secondTab); // add the tab in the TabLayout
Fragment fragment = null;
fragment = new FirstFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.simpleFrameLayout, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
// get the current selected tab's position and replace the fragment accordingly
Fragment fragment = null;
switch (tab.getPosition()) {
case 0:
fragment = new FirstFragment();
myMenu.findItem(R.id.telefono).setVisible(false);
break;
case 1:
fragment = s;
myMenu.findItem(R.id.telefono).setVisible(true);
break;
}
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.simpleFrameLayout, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
public void conectar(String nombre, String ip, int puerto) {
//Code...
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(new Runnable() {
@Override
public void run() {
Looper.prepare();
runOnUiThread(new Runnable() {
@Override
public void run() {
try{
//Code...
try {
//Code
TabLayout.Tab tab = tabLayout.getTabAt(1);
tab.select();
s.roomText(param1);
}
catch(SocketTimeoutException ex)
{
}
}
catch(Exception e){
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String stacktrace = sw.toString();
// create an email intent to send to yourself
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("plain/text");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { "[email protected]" });
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "App Error Report");
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, stacktrace);
// start the email activity - note you need to start it with a chooser
startActivity(Intent.createChooser(emailIntent, "Send error report..."));
}
}
});
}
});
}
}
Второй фрагмент:
package com.mycompany.myapp;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
public class SecondFragment extends Fragment
{
EditText chat, room;
public SecondFragment() {
// Required empty public constructor
}
@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_second, container, false);
room = (EditText) view.findViewById(R.id.mainEditText1);
chat = (EditText) view.findViewById(R.id.chatEditText1);
return view;
}
public void roomText(String t){
room.setText(t);
}
}
Я пытаюсь установить текст editText фрагмента из MainActivity. Для этого я написал в SecondFragment метод, который должен изменить текст этого editText. И этот метод вызывается в MainActivity.
Почему объект «комната» нулевой, если вкладка была выбрана заранее, а элементы SecondFragment фактически уже должны быть созданы? Я не понимаю это.
Я уже разобрался с этим: Что такое NullPointerException и как его исправить?
Но мне это не помогает.
Я видел, что многие пользователи голосуют минус баллы без причины. Поэтому я надеюсь, что этот вопрос приемлем.
Переименовано, так что это не молоток.
Пожалуйста, опишите проблему, прежде чем создавать стену кода.
Переменной room
присваивается значение при вызове onCreateView()
; Это должно произойти после вызова ft.commit()
внутри вашего onTabSelected
метода.
Но поведение commit
по умолчанию асинхронно, поэтому:
FragmentTransaction не применяется сразу после вызова commit()
Основной поток не будет ждать, он просто продолжит выполнение следующих строк кода, независимо от того, было ли commit()
завершено или нет (или даже не запущено).
В результате onCreateView()
from SecondFragment
не был выполнен до вашего вызова основного потока: room
все еще не был инициализирован, когда вы попытались вызвать setText()
на нем.
Чтобы этого избежать, принудительно выполните обновление, вызвав executePendingTransactions
:
//....
ft.replace(R.id.simpleFrameLayout, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
ft.executePendingTransactions();
Немного информации из документации:
executePendingTransactions --- После фиксации FragmentTransaction с помощью FragmentTransaction#commit она должна быть выполнена асинхронно в основном потоке процесса. Если вы хотите немедленно выполнить любые ожидающие операции, вы можете вызвать это функция.
В резюме
executePendingTransactions
предлагает commit
что-то вроде «синхронного» характера. Это заставит процесс немедленно выполнить все задачи, запланированные на FragmentTransaction
.
Таким образом, вы гарантируете, что onCreateView()
будет правильно выполнен (и room
инициализирован) к тому времени, когда вы вызовете s.roomText()
, избегая сбивающего вас с толку NullPointerException
.
Не лучше ли использовать обратный вызов?
Я не понимаю вашей точки зрения. Обратные вызовы — это просто фрагменты кода, которые могут выполняться как синхронно, так и асинхронно. Проблема здесь в синхронизации. Кроме того, я действительно не понимаю смысла реализации синхронного обратного вызова, когда вам просто нужно вызвать метод, чтобы добиться того же поведения.
Весь смысл обратного вызова в том, что он происходит после того, как произошло что-то еще, в данном случае завершение коммита.
На самом деле обратный вызов будет вызываться после коммита. Сразу после: дело в том, что коммит является асинхронным, поэтому он может быть не завершен или даже не запущен к моменту вызова обратного вызова. который находится сразу после строки commit(). У вас будет та же проблема, что и у ОП.
Произошло что-то еще, не означает, что что-то еще было выполнено и/или завершено. На самом деле roomText был вызван после того, как произошла фиксация. Но коммит так и не был выполнен/завершен.
@aran Спасибо за объяснение. Я думал, что что-то подобное происходит в этом направлении... Но я думаю, что никогда бы не узнал об этом.
Детали @Aaron являются ключом к коду. Рад помочь :)
@aran Ну, название слабое, не конкретное. Не уверен, что он заслуживает отрицательного голосования, но это, безусловно, плохой вопрос.