your programing

새 Android 조각을 인스턴스화하는 모범 사례

lovepro 2020. 9. 30. 11:15
반응형

새 Android 조각을 인스턴스화하는 모범 사례


응용 프로그램에서 새 조각을 인스턴스화하는 두 가지 일반적인 관행을 보았습니다.

Fragment newFragment = new MyFragment();

Fragment newFragment = MyFragment.newInstance();

두 번째 옵션은 정적 메서드를 사용 newInstance()하며 일반적으로 다음 메서드를 포함합니다.

public static Fragment newInstance() 
{
    MyFragment myFragment = new MyFragment();
    return myFragment;
}

처음에는 새로운 인스턴스를 생성 할 때 유연성을 제공하기 위해 newInstance () 메서드를 오버로드 할 수 있다는 것이 주된 이점이라고 생각했습니다. 그러나 Fragment에 대해 오버로드 된 생성자를 생성하여이를 수행 할 수도 있습니다.

내가 뭐 놓친 거 없니?

한 접근 방식의 이점은 무엇입니까? 아니면 그냥 좋은 습관입니까?


Android가 나중에 Fragment를 다시 생성하기로 결정하면 Fragment의 인수가없는 생성자를 호출합니다. 따라서 생성자를 오버로드하는 것은 해결책이 아닙니다.

즉, Android에서 Fragment를 다시 만든 후 사용할 수 있도록 Fragment에 항목을 전달하는 setArguments방법 은 번들을 메서드에 전달하는 것입니다.

예를 들어, 조각에 정수를 전달하려면 다음과 같이 사용합니다.

public static MyFragment newInstance(int someInt) {
    MyFragment myFragment = new MyFragment();

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    myFragment.setArguments(args);

    return myFragment;
}

나중에 Fragment에서 다음 onCreate()을 사용하여 해당 정수에 액세스 할 수 있습니다.

getArguments().getInt("someInt", 0);

이 번들은 Android에서 Fragment를 다시 만든 경우에도 사용할 수 있습니다.

또한 참고 : setArgumentsFragment가 활동에 연결되기 전에 만 호출 할 수 있습니다.

이 접근 방식은 Android 개발자 참조 : https://developer.android.com/reference/android/app/Fragment.html 에도 문서화되어 있습니다.


newInstance()내가 본를 사용하는 유일한 이점 은 다음과 같습니다.

  1. 프래그먼트에서 사용하는 모든 인수를 번들로 묶을 수있는 단일 위치가 있으며 프래그먼트를 인스턴스화 할 때마다 아래 코드를 작성할 필요가 없습니다.

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
    
  2. 다른 클래스에게 충실하게 작동 것으로 예상 되는 인수를 알려주는 좋은 방법입니다 (단편 인스턴스에 인수가 번들로 제공되지 않는 경우 케이스를 처리 할 수 ​​있어야하지만).

그래서, 제 생각은 정적 newInstance()사용하여 조각을 인스턴스화하는 것이 좋은 습관이라는 것입니다.


다른 방법도 있습니다.

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)

@yydl은 newInstance방법이 더 나은 이유에 대한 설득력있는 이유를 제공하지만 :

Android가 나중에 Fragment를 다시 생성하기로 결정하면 Fragment의 인수가없는 생성자를 호출합니다. 따라서 생성자를 오버로드하는 것은 해결책이 아닙니다.

생성자 를 사용하는 것은 여전히 ​​가능합니다 . 그 이유를 알아 보려면 먼저 Android에서 위의 해결 방법을 사용하는 이유를 확인해야합니다.

조각을 사용하려면 먼저 인스턴스가 필요합니다. 안드로이드 호출 YourFragment()합니다 ( 아무 인수 생성자) 조각의 인스턴스를 생성합니다. Android에서 사용할 생성자를 알 수 없기 때문에 여기에서 작성한 오버로드 된 생성자는 무시됩니다.

활동의 수명에서 조각은 위와 같이 생성되고 Android에 의해 여러 번 파괴됩니다. 즉, 조각 개체 자체에 데이터를 넣으면 조각이 파괴되면 손실됩니다.

이 문제를 해결하기 위해 android는 에서 액세스 할 수 있는 Bundle(호출 setArguments())을 사용하여 데이터를 저장하도록 요청합니다 YourFragment. Argument bundle는 Android에 의해 보호되므로 영구적 인 것으로 보장됩니다 .

이 번들을 설정하는 한 가지 방법은 정적 newInstance메서드 를 사용하는 것입니다.

public static YourFragment newInstance (int data) {
    YourFragment yf = new YourFragment()
    /* See this code gets executed immediately on your object construction */
    Bundle args = new Bundle();
    args.putInt("data", data);
    yf.setArguments(args);
    return yf;
}

그러나 생성자 :

public YourFragment(int data) {
    Bundle args = new Bundle();
    args.putInt("data", data);
    setArguments(args);
}

newInstance방법 과 똑같은 일을 할 수 있습니다 .

당연히 이것은 실패 할 것이며 Android가이 newInstance방법 을 사용하기를 원하는 이유 중 하나입니다 .

public YourFragment(int data) {
    this.data = data; // Don't do this
}

추가 설명으로 Android의 Fragment 클래스는 다음과 같습니다.

/**
 * Supply the construction arguments for this fragment.  This can only
 * be called before the fragment has been attached to its activity; that
 * is, you should call it immediately after constructing the fragment.  The
 * arguments supplied here will be retained across fragment destroy and
 * creation.
 */
public void setArguments(Bundle args) {
    if (mIndex >= 0) {
        throw new IllegalStateException("Fragment already active");
    }
    mArguments = args;
}

Android는 인수가 생성시 에만 설정되도록 요청하고 이러한 인수 가 유지되도록 보장합니다.

EDIT: As pointed out in the comments by @JHH, if you are providing a custom constructor that requires some arguments, then Java won't provide your fragment with a no arg default constructor. So this would require you to define a no arg constructor, which is code that you could avoid with the newInstance factory method.

EDIT: Android doesn't allow using an overloaded constructor for fragments anymore. You must use the newInstance method.


I disagree with yydi answer saying:

If Android decides to recreate your Fragment later, it's going to call the no-argument constructor of your fragment. So overloading the constructor is not a solution.

I think it is a solution and a good one, this is exactly the reason it been developed by Java core language.

Its true that Android system can destroy and recreate your Fragment. So you can do this:

public MyFragment() {
//  An empty constructor for Android System to use, otherwise exception may occur.
}

public MyFragment(int someInt) {
    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    setArguments(args);
}

It will allow you to pull someInt from getArguments() latter on, even if the Fragment been recreated by the system. This is more elegant solution than static constructor.

For my opinion static constructors are useless and should not be used. Also they will limit you if in the future you would like to extend this Fragment and add more functionality to the constructor. With static constructor you can't do this.

Update:

Android added inspection that flag all non-default constructors with an error.
I recommend to disable it, for the reasons mentioned above.


Some kotlin code:

companion object {
    fun newInstance(first: String, second: String) : SampleFragment {
        return SampleFragment().apply {
            arguments = Bundle().apply {
                putString("firstString", first)
                putString("secondString", second)
            }
        }
    }
}

And you can get arguments with this:

val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}

Best practice to instance fragments with arguments in android is to have static factory method in your fragment.

public static MyFragment newInstance(String name, int age) {
    Bundle bundle = new Bundle();
    bundle.putString("name", name);
    bundle.putInt("age", age);

    MyFragment fragment = new MyFragment();
    fragment.setArguments(bundle);

    return fragment;
}

You should avoid setting your fields with the instance of a fragment. Because whenever android system recreate your fragment, if it feels that the system needs more memory, than it will recreate your fragment by using constructor with no arguments.

You can find more info about best practice to instantiate fragments with arguments here.


Since the questions about best practice, I would add, that very often good idea to use hybrid approach for creating fragment when working with some REST web services

We can't pass complex objects, for example some User model, for case of displaying user fragment

But what we can do, is to check in onCreate that user!=null and if not - then bring him from data layer, otherwise - use existing.

This way we gain both ability to recreate by userId in case of fragment recreation by Android and snappiness for user actions, as well as ability to create fragments by holding to object itself or only it's id

Something likes this:

public class UserFragment extends Fragment {
    public final static String USER_ID="user_id";
    private User user;
    private long userId;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);
        if(user==null){
            //
            // Recreating here user from user id(i.e requesting from your data model,
            // which could be services, direct request to rest, or data layer sitting
            // on application model
            //
             user = bringUser();
        }
    }

    public static UserFragment newInstance(User user, long user_id){
        UserFragment userFragment = new UserFragment();
        Bundle args = new Bundle();
        args.putLong(USER_ID,user_id);
        if(user!=null){
            userFragment.user=user;
        }
        userFragment.setArguments(args);
        return userFragment;

    }

    public static UserFragment newInstance(long user_id){
        return newInstance(null,user_id);
    }

    public static UserFragment newInstance(User user){
        return newInstance(user,user.id);
    }
}

Best way to instantiate the fragment is use default Fragment.instantiate method or create factory method to instantiate the the fragment
Caution: always create one empty constructor in fragment other while restoring fragment memory will throw run-time exception.


setArguments() is useless. It only brings a mess.

public class MyFragment extends Fragment {

    public String mTitle;
    public String mInitialTitle;

    public static MyFragment newInstance(String param1) {
        MyFragment f = new MyFragment();
        f.mInitialTitle = param1;
        f.mTitle = param1;
        return f;
    }

    @Override
    public void onSaveInstanceState(Bundle state) {
        state.putString("mInitialTitle", mInitialTitle);
        state.putString("mTitle", mTitle);
        super.onSaveInstanceState(state);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
        if (state != null) {
            mInitialTitle = state.getString("mInitialTitle");
            mTitle = state.getString("mTitle");
        } 
        ...
    }
}

I believe I have a much simpeler solution for this.

public class MyFragment extends Fragment{

   private String mTitle;
   private List<MyObject> mObjects;

   public static MyFragment newInstance(String title, List<MyObject> objects)
   MyFragment myFrag = new MyFragment();
   myFrag.mTitle = title;
   myFrag.mObjects = objects;
   return myFrag;
   }

참고URL : https://stackoverflow.com/questions/9245408/best-practice-for-instantiating-a-new-android-fragment

반응형