Android프로그래밍

[Android 15] 해결방법 targetSdk 35 :: SnackbarLayout can only be accessed from within the same library group (referenced groupId=com.google.android.material from groupId=…)

이 오류는 Android의 Snackbar.SnackbarLayout 클래스를 직접 참조했기 때문에 발생한 것이다.

Android 13 (API 33) 이상, 특히 targetSdkVersion 34+ (예: 35에서는 더 엄격해짐)에서는 @RestrictTo(LIBRARY_GROUP)로 지정된 내부 클래스에 접근하는 것이 금지된다.

해당 클래스는 com.google.android.material 라이브러리 내부에서만 사용되어야 한다.


현재 문제 코드

Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) globalSnackbar.getView();

SnackbarLayout은 내부 전용 API로, 외부 앱에서 직접 사용할 수 없다.


해결 방법

Snackbar.getView() 로 반환되는 뷰에 대해 View 타입으로만 접근

Snackbar.getView()View 타입을 반환하므로, 내부 클래스에 캐스팅하지 말고 그냥 View로 쓰자.

View layout = globalSnackbar.getView();
layout.setPadding(0, 0, 0, 0); // 필요에 따라 setPadding 가능


추가 커스터마이징이 필요할 경우?

Snackbar의 레이아웃을 더 복잡하게 커스터마이징하려면:

  • Snackbar 대신 Custom Toast, BottomSheetDialog, 또는 Custom View를 사용하는 것을 추천.
  • 또는 Snackbar의 기본 메시지 뷰(TextView)에 접근하려면 아래처럼 사용할 수 있다

TextView textView = layout.findViewById(com.google.android.material.R.id.snackbar_text);
textView.setTextSize(18); // 텍스트 크기 변경 예시


정리

항목조치
🚫내부 클래스 접근Snackbar.SnackbarLayout 직접 참조 금지
대체 방법View layout = snackbar.getView();
커스터마이즈layout.findViewById(...) 등으로 내부 요소만 접근
필요시 대안Custom Toast, BottomSheetDialog 등 고려


CustomSnackbarView 만들기

필요하다면 커스터마이즈 가능한 Snackbar 대체 뷰를 직접 만들어볼 수 있다.

기존 Snackbar 대신 사용할 수 있는 커스터마이즈 가능한 대체 뷰 (CustomSnackbarView) 를 만들어 보자. Toast보다 자유도가 높고 Snackbar처럼 하단에 나타나며, 버튼이나 애니메이션도 추가할 수 있도록 구성한다.


목표

  1. 하단에 슬라이드처럼 올라오는 알림
  2. 메시지 + 버튼 (옵션)
  3. 일정 시간 후 자동 사라짐
  4. 스타일, 색상, 애니메이션 자유롭게 커스터마이징 가능


CustomSnackbar.java

import android.app.Activity;
import android.os.Handler;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.TextView;

public class CustomSnackbar {

    private final View snackbarView;
    private final Handler handler = new Handler();

    public CustomSnackbar(Activity activity, String message, String buttonText, View.OnClickListener action) {
        LayoutInflater inflater = activity.getLayoutInflater();
        snackbarView = inflater.inflate(R.layout.view_custom_snackbar, null);

        TextView messageView = snackbarView.findViewById(R.id.snackbar_text);
        messageView.setText(message);

        Button actionButton = snackbarView.findViewById(R.id.snackbar_action);
        if (buttonText != null && action != null) {
            actionButton.setText(buttonText);
            actionButton.setOnClickListener(v -> {
                action.onClick(v);
                dismiss();
            });
        } else {
            actionButton.setVisibility(View.GONE);
        }

        ViewGroup root = activity.findViewById(android.R.id.content);
        root.addView(snackbarView);

        animateIn();

        // Auto dismiss after 3 seconds
        handler.postDelayed(this::dismiss, 3000);
    }

    private void animateIn() {
        TranslateAnimation animate = new TranslateAnimation(
                0, 0, snackbarView.getHeight(), 0);
        animate.setDuration(300);
        snackbarView.startAnimation(animate);
        snackbarView.setVisibility(View.VISIBLE);
    }

    public void dismiss() {
        TranslateAnimation animate = new TranslateAnimation(
                0, 0, 0, snackbarView.getHeight());
        animate.setDuration(300);
        snackbarView.startAnimation(animate);
        snackbarView.setVisibility(View.GONE);

        // Remove view after animation
        new Handler().postDelayed(() -> {
            ((ViewGroup) snackbarView.getParent()).removeView(snackbarView);
        }, 300);
    }
}


view_custom_snackbar.xml (res/layout)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/snackbar_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/black"
    android:padding="16dp"
    android:orientation="horizontal"
    android:gravity="center_vertical"
    android:layout_gravity="bottom"
    android:elevation="6dp">

    <TextView
        android:id="@+id/snackbar_text"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Message goes here" />

    <Button
        android:id="@+id/snackbar_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/holo_orange_light"
        android:text="OK"
        android:background="?attr/selectableItemBackground"
        android:minWidth="0dp"
        android:minHeight="0dp"
        android:padding="8dp"/>
</LinearLayout>

사용 예시

new CustomSnackbar(
    MainActivity.this,
    "저장되었습니다!",
    "실행취소",
    v -> {
        // 취소 작업 실행
    }
);

또는 버튼 없이 단순 메시지만 표시할 수도 있다

new CustomSnackbar(MainActivity.this, "알림 내용입니다", null, null);

error: Content is protected !!